@angular/core
Version:
Angular - the core framework
1,074 lines • 174 kB
JavaScript
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as tslib_1 from "tslib";
import '../util/ng_i18n_closure_mode';
import { getPluralCase } from '../i18n/localization';
import { SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS, getTemplateContent } from '../sanitization/html_sanitizer';
import { InertBodyHelper } from '../sanitization/inert_body';
import { _sanitizeUrl, sanitizeSrcset } from '../sanitization/url_sanitizer';
import { addAllToArray } from '../util/array_utils';
import { assertDataInRange, assertDefined, assertEqual, assertGreaterThan } from '../util/assert';
import { attachPatchData } from './context_discovery';
import { bind, setDelayProjection, ɵɵload } from './instructions/all';
import { attachI18nOpCodesDebug } from './instructions/lview_debug';
import { allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, textBindingInternal } from './instructions/shared';
import { NATIVE } from './interfaces/container';
import { COMMENT_MARKER, ELEMENT_MARKER } from './interfaces/i18n';
import { BINDING_INDEX, HEADER_OFFSET, RENDERER, TVIEW, T_HOST } from './interfaces/view';
import { appendChild, appendProjectedNodes, createTextNode, nativeRemoveNode } from './node_manipulation';
import { getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode } from './state';
import { NO_CHANGE } from './tokens';
import { renderStringify } from './util/misc_utils';
import { findComponentView } from './util/view_traversal_utils';
import { getNativeByIndex, getNativeByTNode, getTNode, isLContainer } from './util/view_utils';
var MARKER = "\uFFFD";
var ICU_BLOCK_REGEXP = /^\s*(�\d+:?\d*�)\s*,\s*(select|plural)\s*,/;
var SUBTEMPLATE_REGEXP = /�\/?\*(\d+:\d+)�/gi;
var PH_REGEXP = /�(\/?[#*!]\d+):?\d*�/gi;
var BINDING_REGEXP = /�(\d+):?\d*�/gi;
var ICU_REGEXP = /({\s*�\d+:?\d*�\s*,\s*\S{6}\s*,[\s\S]*})/gi;
// i18nPostprocess consts
var ROOT_TEMPLATE_ID = 0;
var PP_MULTI_VALUE_PLACEHOLDERS_REGEXP = /\[(�.+?�?)\]/;
var PP_PLACEHOLDERS_REGEXP = /\[(�.+?�?)\]|(�\/?\*\d+:\d+�)/g;
var PP_ICU_VARS_REGEXP = /({\s*)(VAR_(PLURAL|SELECT)(_\d+)?)(\s*,)/g;
var PP_ICUS_REGEXP = /�I18N_EXP_(ICU(_\d+)?)�/g;
var PP_CLOSE_TEMPLATE_REGEXP = /\/\*/;
var PP_TEMPLATE_ID_REGEXP = /\d+\:(\d+)/;
/**
* Breaks pattern into strings and top level {...} blocks.
* Can be used to break a message into text and ICU expressions, or to break an ICU expression into
* keys and cases.
* Original code from closure library, modified for Angular.
*
* @param pattern (sub)Pattern to be broken.
*
*/
function extractParts(pattern) {
if (!pattern) {
return [];
}
var prevPos = 0;
var braceStack = [];
var results = [];
var braces = /[{}]/g;
// lastIndex doesn't get set to 0 so we have to.
braces.lastIndex = 0;
var match;
while (match = braces.exec(pattern)) {
var pos = match.index;
if (match[0] == '}') {
braceStack.pop();
if (braceStack.length == 0) {
// End of the block.
var block = pattern.substring(prevPos, pos);
if (ICU_BLOCK_REGEXP.test(block)) {
results.push(parseICUBlock(block));
}
else {
results.push(block);
}
prevPos = pos + 1;
}
}
else {
if (braceStack.length == 0) {
var substring_1 = pattern.substring(prevPos, pos);
results.push(substring_1);
prevPos = pos + 1;
}
braceStack.push('{');
}
}
var substring = pattern.substring(prevPos);
results.push(substring);
return results;
}
/**
* Parses text containing an ICU expression and produces a JSON object for it.
* Original code from closure library, modified for Angular.
*
* @param pattern Text containing an ICU expression that needs to be parsed.
*
*/
function parseICUBlock(pattern) {
var cases = [];
var values = [];
var icuType = 1 /* plural */;
var mainBinding = 0;
pattern = pattern.replace(ICU_BLOCK_REGEXP, function (str, binding, type) {
if (type === 'select') {
icuType = 0 /* select */;
}
else {
icuType = 1 /* plural */;
}
mainBinding = parseInt(binding.substr(1), 10);
return '';
});
var parts = extractParts(pattern);
// Looking for (key block)+ sequence. One of the keys has to be "other".
for (var pos = 0; pos < parts.length;) {
var key = parts[pos++].trim();
if (icuType === 1 /* plural */) {
// Key can be "=x", we just want "x"
key = key.replace(/\s*(?:=)?(\w+)\s*/, '$1');
}
if (key.length) {
cases.push(key);
}
var blocks = extractParts(parts[pos++]);
if (cases.length > values.length) {
values.push(blocks);
}
}
assertGreaterThan(cases.indexOf('other'), -1, 'Missing key "other" in ICU statement.');
// TODO(ocombe): support ICU expressions in attributes, see #21615
return { type: icuType, mainBinding: mainBinding, cases: cases, values: values };
}
/**
* Removes everything inside the sub-templates of a message.
*/
function removeInnerTemplateTranslation(message) {
var match;
var res = '';
var index = 0;
var inTemplate = false;
var tagMatched;
while ((match = SUBTEMPLATE_REGEXP.exec(message)) !== null) {
if (!inTemplate) {
res += message.substring(index, match.index + match[0].length);
tagMatched = match[1];
inTemplate = true;
}
else {
if (match[0] === MARKER + "/*" + tagMatched + MARKER) {
index = match.index;
inTemplate = false;
}
}
}
ngDevMode &&
assertEqual(inTemplate, false, "Tag mismatch: unable to find the end of the sub-template in the translation \"" + message + "\"");
res += message.substr(index);
return res;
}
/**
* Extracts a part of a message and removes the rest.
*
* This method is used for extracting a part of the message associated with a template. A translated
* message can span multiple templates.
*
* Example:
* ```
* <div i18n>Translate <span *ngIf>me</span>!</div>
* ```
*
* @param message The message to crop
* @param subTemplateIndex Index of the sub-template to extract. If undefined it returns the
* external template and removes all sub-templates.
*/
export function getTranslationForTemplate(message, subTemplateIndex) {
if (typeof subTemplateIndex !== 'number') {
// We want the root template message, ignore all sub-templates
return removeInnerTemplateTranslation(message);
}
else {
// We want a specific sub-template
var start = message.indexOf(":" + subTemplateIndex + MARKER) + 2 + subTemplateIndex.toString().length;
var end = message.search(new RegExp(MARKER + "\\/\\*\\d+:" + subTemplateIndex + MARKER));
return removeInnerTemplateTranslation(message.substring(start, end));
}
}
/**
* Generate the OpCodes to update the bindings of a string.
*
* @param str The string containing the bindings.
* @param destinationNode Index of the destination node which will receive the binding.
* @param attrName Name of the attribute, if the string belongs to an attribute.
* @param sanitizeFn Sanitization function used to sanitize the string after update, if necessary.
*/
function generateBindingUpdateOpCodes(str, destinationNode, attrName, sanitizeFn) {
if (sanitizeFn === void 0) { sanitizeFn = null; }
var updateOpCodes = [null, null]; // Alloc space for mask and size
var textParts = str.split(BINDING_REGEXP);
var mask = 0;
for (var j = 0; j < textParts.length; j++) {
var textValue = textParts[j];
if (j & 1) {
// Odd indexes are bindings
var bindingIndex = parseInt(textValue, 10);
updateOpCodes.push(-1 - bindingIndex);
mask = mask | toMaskBit(bindingIndex);
}
else if (textValue !== '') {
// Even indexes are text
updateOpCodes.push(textValue);
}
}
updateOpCodes.push(destinationNode << 2 /* SHIFT_REF */ |
(attrName ? 1 /* Attr */ : 0 /* Text */));
if (attrName) {
updateOpCodes.push(attrName, sanitizeFn);
}
updateOpCodes[0] = mask;
updateOpCodes[1] = updateOpCodes.length - 2;
return updateOpCodes;
}
function getBindingMask(icuExpression, mask) {
if (mask === void 0) { mask = 0; }
mask = mask | toMaskBit(icuExpression.mainBinding);
var match;
for (var i = 0; i < icuExpression.values.length; i++) {
var valueArr = icuExpression.values[i];
for (var j = 0; j < valueArr.length; j++) {
var value = valueArr[j];
if (typeof value === 'string') {
while (match = BINDING_REGEXP.exec(value)) {
mask = mask | toMaskBit(parseInt(match[1], 10));
}
}
else {
mask = getBindingMask(value, mask);
}
}
}
return mask;
}
var i18nIndexStack = [];
var i18nIndexStackPointer = -1;
/**
* Convert binding index to mask bit.
*
* Each index represents a single bit on the bit-mask. Because bit-mask only has 32 bits, we make
* the 32nd bit share all masks for all bindings higher than 32. Since it is extremely rare to have
* more than 32 bindings this will be hit very rarely. The downside of hitting this corner case is
* that we will execute binding code more often than necessary. (penalty of performance)
*/
function toMaskBit(bindingIndex) {
return 1 << Math.min(bindingIndex, 31);
}
var parentIndexStack = [];
/**
* Marks a block of text as translatable.
*
* The instructions `i18nStart` and `i18nEnd` mark the translation block in the template.
* The translation `message` is the value which is locale specific. The translation string may
* contain placeholders which associate inner elements and sub-templates within the translation.
*
* The translation `message` placeholders are:
* - `�{index}(:{block})�`: *Binding Placeholder*: Marks a location where an expression will be
* interpolated into. The placeholder `index` points to the expression binding index. An optional
* `block` that matches the sub-template in which it was declared.
* - `�#{index}(:{block})�`/`�/#{index}(:{block})�`: *Element Placeholder*: Marks the beginning
* and end of DOM element that were embedded in the original translation block. The placeholder
* `index` points to the element index in the template instructions set. An optional `block` that
* matches the sub-template in which it was declared.
* - `�!{index}(:{block})�`/`�/!{index}(:{block})�`: *Projection Placeholder*: Marks the
* beginning and end of <ng-content> that was embedded in the original translation block.
* The placeholder `index` points to the element index in the template instructions set.
* An optional `block` that matches the sub-template in which it was declared.
* - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be
* split up and translated separately in each angular template function. The `index` points to the
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
*
* @param index A unique index of the translation in the static block.
* @param message The translation message.
* @param subTemplateIndex Optional sub-template index in the `message`.
*
* @codeGenApi
*/
export function ɵɵi18nStart(index, message, subTemplateIndex) {
var tView = getLView()[TVIEW];
ngDevMode && assertDefined(tView, "tView should be defined");
i18nIndexStack[++i18nIndexStackPointer] = index;
// We need to delay projections until `i18nEnd`
setDelayProjection(true);
if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) {
i18nStartFirstPass(tView, index, message, subTemplateIndex);
}
}
// Count for the number of vars that will be allocated for each i18n block.
// It is global because this is used in multiple functions that include loops and recursive calls.
// This is reset to 0 when `i18nStartFirstPass` is called.
var i18nVarsCount;
/**
* See `i18nStart` above.
*/
function i18nStartFirstPass(tView, index, message, subTemplateIndex) {
var viewData = getLView();
var startIndex = tView.blueprint.length - HEADER_OFFSET;
i18nVarsCount = 0;
var previousOrParentTNode = getPreviousOrParentTNode();
var parentTNode = getIsParent() ? getPreviousOrParentTNode() :
previousOrParentTNode && previousOrParentTNode.parent;
var parentIndex = parentTNode && parentTNode !== viewData[T_HOST] ? parentTNode.index - HEADER_OFFSET : index;
var parentIndexPointer = 0;
parentIndexStack[parentIndexPointer] = parentIndex;
var createOpCodes = [];
// If the previous node wasn't the direct parent then we have a translation without top level
// element and we need to keep a reference of the previous element if there is one
if (index > 0 && previousOrParentTNode !== parentTNode) {
// Create an OpCode to select the previous TNode
createOpCodes.push(previousOrParentTNode.index << 3 /* SHIFT_REF */ | 0 /* Select */);
}
var updateOpCodes = [];
var icuExpressions = [];
var templateTranslation = getTranslationForTemplate(message, subTemplateIndex);
var msgParts = templateTranslation.split(PH_REGEXP);
for (var i = 0; i < msgParts.length; i++) {
var value = msgParts[i];
if (i & 1) {
// Odd indexes are placeholders (elements and sub-templates)
if (value.charAt(0) === '/') {
// It is a closing tag
if (value.charAt(1) === "#" /* ELEMENT */) {
var phIndex = parseInt(value.substr(2), 10);
parentIndex = parentIndexStack[--parentIndexPointer];
createOpCodes.push(phIndex << 3 /* SHIFT_REF */ | 5 /* ElementEnd */);
}
}
else {
var phIndex = parseInt(value.substr(1), 10);
// The value represents a placeholder that we move to the designated index
createOpCodes.push(phIndex << 3 /* SHIFT_REF */ | 0 /* Select */, parentIndex << 17 /* SHIFT_PARENT */ | 1 /* AppendChild */);
if (value.charAt(0) === "#" /* ELEMENT */) {
parentIndexStack[++parentIndexPointer] = parentIndex = phIndex;
}
}
}
else {
// Even indexes are text (including bindings & ICU expressions)
var parts = extractParts(value);
for (var j = 0; j < parts.length; j++) {
if (j & 1) {
// Odd indexes are ICU expressions
// Create the comment node that will anchor the ICU expression
var icuNodeIndex = startIndex + i18nVarsCount++;
createOpCodes.push(COMMENT_MARKER, ngDevMode ? "ICU " + icuNodeIndex : '', icuNodeIndex, parentIndex << 17 /* SHIFT_PARENT */ | 1 /* AppendChild */);
// Update codes for the ICU expression
var icuExpression = parts[j];
var mask = getBindingMask(icuExpression);
icuStart(icuExpressions, icuExpression, icuNodeIndex, icuNodeIndex);
// Since this is recursive, the last TIcu that was pushed is the one we want
var tIcuIndex = icuExpressions.length - 1;
updateOpCodes.push(toMaskBit(icuExpression.mainBinding), // mask of the main binding
3, // skip 3 opCodes if not changed
-1 - icuExpression.mainBinding, icuNodeIndex << 2 /* SHIFT_REF */ | 2 /* IcuSwitch */, tIcuIndex, mask, // mask of all the bindings of this ICU expression
2, // skip 2 opCodes if not changed
icuNodeIndex << 2 /* SHIFT_REF */ | 3 /* IcuUpdate */, tIcuIndex);
}
else if (parts[j] !== '') {
var text = parts[j];
// Even indexes are text (including bindings)
var hasBinding = text.match(BINDING_REGEXP);
// Create text nodes
var textNodeIndex = startIndex + i18nVarsCount++;
createOpCodes.push(
// If there is a binding, the value will be set during update
hasBinding ? '' : text, textNodeIndex, parentIndex << 17 /* SHIFT_PARENT */ | 1 /* AppendChild */);
if (hasBinding) {
addAllToArray(generateBindingUpdateOpCodes(text, textNodeIndex), updateOpCodes);
}
}
}
}
}
allocExpando(viewData, i18nVarsCount);
ngDevMode &&
attachI18nOpCodesDebug(createOpCodes, updateOpCodes, icuExpressions.length ? icuExpressions : null, viewData);
// NOTE: local var needed to properly assert the type of `TI18n`.
var tI18n = {
vars: i18nVarsCount,
create: createOpCodes,
update: updateOpCodes,
icus: icuExpressions.length ? icuExpressions : null,
};
tView.data[index + HEADER_OFFSET] = tI18n;
}
function appendI18nNode(tNode, parentTNode, previousTNode, viewData) {
ngDevMode && ngDevMode.rendererMoveNode++;
var nextNode = tNode.next;
if (!previousTNode) {
previousTNode = parentTNode;
}
// Re-organize node tree to put this node in the correct position.
if (previousTNode === parentTNode && tNode !== parentTNode.child) {
tNode.next = parentTNode.child;
parentTNode.child = tNode;
}
else if (previousTNode !== parentTNode && tNode !== previousTNode.next) {
tNode.next = previousTNode.next;
previousTNode.next = tNode;
}
else {
tNode.next = null;
}
if (parentTNode !== viewData[T_HOST]) {
tNode.parent = parentTNode;
}
// If tNode was moved around, we might need to fix a broken link.
var cursor = tNode.next;
while (cursor) {
if (cursor.next === tNode) {
cursor.next = nextNode;
}
cursor = cursor.next;
}
// If the placeholder to append is a projection, we need to move the projected nodes instead
if (tNode.type === 1 /* Projection */) {
var tProjectionNode = tNode;
appendProjectedNodes(viewData, tProjectionNode, tProjectionNode.projection, findComponentView(viewData));
return tNode;
}
appendChild(getNativeByTNode(tNode, viewData), tNode, viewData);
var slotValue = viewData[tNode.index];
if (tNode.type !== 0 /* Container */ && isLContainer(slotValue)) {
// Nodes that inject ViewContainerRef also have a comment node that should be moved
appendChild(slotValue[NATIVE], tNode, viewData);
}
return tNode;
}
/**
* Handles message string post-processing for internationalization.
*
* Handles message string post-processing by transforming it from intermediate
* format (that might contain some markers that we need to replace) to the final
* form, consumable by i18nStart instruction. Post processing steps include:
*
* 1. Resolve all multi-value cases (like [�*1:1��#2:1�|�#4:1�|�5�])
* 2. Replace all ICU vars (like "VAR_PLURAL")
* 3. Replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�)
* in case multiple ICUs have the same placeholder name
*
* @param message Raw translation string for post processing
* @param replacements Set of replacements that should be applied
*
* @returns Transformed string that can be consumed by i18nStart instruction
*
* @codeGenApi
*/
export function ɵɵi18nPostprocess(message, replacements) {
if (replacements === void 0) { replacements = {}; }
/**
* Step 1: resolve all multi-value placeholders like [�#5�|�*1:1��#2:1�|�#4:1�]
*
* Note: due to the way we process nested templates (BFS), multi-value placeholders are typically
* grouped by templates, for example: [�#5�|�#6�|�#1:1�|�#3:2�] where �#5� and �#6� belong to root
* template, �#1:1� belong to nested template with index 1 and �#1:2� - nested template with index
* 3. However in real templates the order might be different: i.e. �#1:1� and/or �#3:2� may go in
* front of �#6�. The post processing step restores the right order by keeping track of the
* template id stack and looks for placeholders that belong to the currently active template.
*/
var result = message;
if (PP_MULTI_VALUE_PLACEHOLDERS_REGEXP.test(message)) {
var matches_1 = {};
var templateIdsStack_1 = [ROOT_TEMPLATE_ID];
result = result.replace(PP_PLACEHOLDERS_REGEXP, function (m, phs, tmpl) {
var content = phs || tmpl;
var placeholders = matches_1[content] || [];
if (!placeholders.length) {
content.split('|').forEach(function (placeholder) {
var match = placeholder.match(PP_TEMPLATE_ID_REGEXP);
var templateId = match ? parseInt(match[1], 10) : ROOT_TEMPLATE_ID;
var isCloseTemplateTag = PP_CLOSE_TEMPLATE_REGEXP.test(placeholder);
placeholders.push([templateId, isCloseTemplateTag, placeholder]);
});
matches_1[content] = placeholders;
}
if (!placeholders.length) {
throw new Error("i18n postprocess: unmatched placeholder - " + content);
}
var currentTemplateId = templateIdsStack_1[templateIdsStack_1.length - 1];
var idx = 0;
// find placeholder index that matches current template id
for (var i = 0; i < placeholders.length; i++) {
if (placeholders[i][0] === currentTemplateId) {
idx = i;
break;
}
}
// update template id stack based on the current tag extracted
var _a = tslib_1.__read(placeholders[idx], 3), templateId = _a[0], isCloseTemplateTag = _a[1], placeholder = _a[2];
if (isCloseTemplateTag) {
templateIdsStack_1.pop();
}
else if (currentTemplateId !== templateId) {
templateIdsStack_1.push(templateId);
}
// remove processed tag from the list
placeholders.splice(idx, 1);
return placeholder;
});
}
// return current result if no replacements specified
if (!Object.keys(replacements).length) {
return result;
}
/**
* Step 2: replace all ICU vars (like "VAR_PLURAL")
*/
result = result.replace(PP_ICU_VARS_REGEXP, function (match, start, key, _type, _idx, end) {
return replacements.hasOwnProperty(key) ? "" + start + replacements[key] + end : match;
});
/**
* Step 3: replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�) in case
* multiple ICUs have the same placeholder name
*/
result = result.replace(PP_ICUS_REGEXP, function (match, key) {
if (replacements.hasOwnProperty(key)) {
var list = replacements[key];
if (!list.length) {
throw new Error("i18n postprocess: unmatched ICU - " + match + " with key: " + key);
}
return list.shift();
}
return match;
});
return result;
}
/**
* Translates a translation block marked by `i18nStart` and `i18nEnd`. It inserts the text/ICU nodes
* into the render tree, moves the placeholder nodes and removes the deleted nodes.
*
* @codeGenApi
*/
export function ɵɵi18nEnd() {
var tView = getLView()[TVIEW];
ngDevMode && assertDefined(tView, "tView should be defined");
i18nEndFirstPass(tView);
// Stop delaying projections
setDelayProjection(false);
}
/**
* See `i18nEnd` above.
*/
function i18nEndFirstPass(tView) {
var viewData = getLView();
ngDevMode && assertEqual(viewData[BINDING_INDEX], viewData[TVIEW].bindingStartIndex, 'i18nEnd should be called before any binding');
var rootIndex = i18nIndexStack[i18nIndexStackPointer--];
var tI18n = tView.data[rootIndex + HEADER_OFFSET];
ngDevMode && assertDefined(tI18n, "You should call i18nStart before i18nEnd");
// Find the last node that was added before `i18nEnd`
var lastCreatedNode = getPreviousOrParentTNode();
// Read the instructions to insert/move/remove DOM elements
var visitedNodes = readCreateOpCodes(rootIndex, tI18n.create, tI18n.icus, viewData);
// Remove deleted nodes
for (var i = rootIndex + 1; i <= lastCreatedNode.index - HEADER_OFFSET; i++) {
if (visitedNodes.indexOf(i) === -1) {
removeNode(i, viewData);
}
}
}
/**
* Creates and stores the dynamic TNode, and unhooks it from the tree for now.
*/
function createDynamicNodeAtIndex(lView, index, type, native, name) {
var previousOrParentTNode = getPreviousOrParentTNode();
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
lView[index + HEADER_OFFSET] = native;
var tNode = getOrCreateTNode(lView[TVIEW], lView[T_HOST], index, type, name, null);
// We are creating a dynamic node, the previous tNode might not be pointing at this node.
// We will link ourselves into the tree later with `appendI18nNode`.
if (previousOrParentTNode.next === tNode) {
previousOrParentTNode.next = null;
}
return tNode;
}
function readCreateOpCodes(index, createOpCodes, icus, viewData) {
var renderer = getLView()[RENDERER];
var currentTNode = null;
var previousTNode = null;
var visitedNodes = [];
for (var i = 0; i < createOpCodes.length; i++) {
var opCode = createOpCodes[i];
if (typeof opCode == 'string') {
var textRNode = createTextNode(opCode, renderer);
var textNodeIndex = createOpCodes[++i];
ngDevMode && ngDevMode.rendererCreateTextNode++;
previousTNode = currentTNode;
currentTNode =
createDynamicNodeAtIndex(viewData, textNodeIndex, 3 /* Element */, textRNode, null);
visitedNodes.push(textNodeIndex);
setIsNotParent();
}
else if (typeof opCode == 'number') {
switch (opCode & 7 /* MASK_OPCODE */) {
case 1 /* AppendChild */:
var destinationNodeIndex = opCode >>> 17 /* SHIFT_PARENT */;
var destinationTNode = void 0;
if (destinationNodeIndex === index) {
// If the destination node is `i18nStart`, we don't have a
// top-level node and we should use the host node instead
destinationTNode = viewData[T_HOST];
}
else {
destinationTNode = getTNode(destinationNodeIndex, viewData);
}
ngDevMode &&
assertDefined(currentTNode, "You need to create or select a node before you can insert it into the DOM");
previousTNode = appendI18nNode(currentTNode, destinationTNode, previousTNode, viewData);
break;
case 0 /* Select */:
var nodeIndex = opCode >>> 3 /* SHIFT_REF */;
visitedNodes.push(nodeIndex);
previousTNode = currentTNode;
currentTNode = getTNode(nodeIndex, viewData);
if (currentTNode) {
setPreviousOrParentTNode(currentTNode, currentTNode.type === 3 /* Element */);
}
break;
case 5 /* ElementEnd */:
var elementIndex = opCode >>> 3 /* SHIFT_REF */;
previousTNode = currentTNode = getTNode(elementIndex, viewData);
setPreviousOrParentTNode(currentTNode, false);
break;
case 4 /* Attr */:
var elementNodeIndex = opCode >>> 3 /* SHIFT_REF */;
var attrName = createOpCodes[++i];
var attrValue = createOpCodes[++i];
// This code is used for ICU expressions only, since we don't support
// directives/components in ICUs, we don't need to worry about inputs here
elementAttributeInternal(elementNodeIndex, attrName, attrValue, viewData);
break;
default:
throw new Error("Unable to determine the type of mutate operation for \"" + opCode + "\"");
}
}
else {
switch (opCode) {
case COMMENT_MARKER:
var commentValue = createOpCodes[++i];
var commentNodeIndex = createOpCodes[++i];
ngDevMode && assertEqual(typeof commentValue, 'string', "Expected \"" + commentValue + "\" to be a comment node value");
var commentRNode = renderer.createComment(commentValue);
ngDevMode && ngDevMode.rendererCreateComment++;
previousTNode = currentTNode;
currentTNode = createDynamicNodeAtIndex(viewData, commentNodeIndex, 5 /* IcuContainer */, commentRNode, null);
visitedNodes.push(commentNodeIndex);
attachPatchData(commentRNode, viewData);
currentTNode.activeCaseIndex = null;
// We will add the case nodes later, during the update phase
setIsNotParent();
break;
case ELEMENT_MARKER:
var tagNameValue = createOpCodes[++i];
var elementNodeIndex = createOpCodes[++i];
ngDevMode && assertEqual(typeof tagNameValue, 'string', "Expected \"" + tagNameValue + "\" to be an element node tag name");
var elementRNode = renderer.createElement(tagNameValue);
ngDevMode && ngDevMode.rendererCreateElement++;
previousTNode = currentTNode;
currentTNode = createDynamicNodeAtIndex(viewData, elementNodeIndex, 3 /* Element */, elementRNode, tagNameValue);
visitedNodes.push(elementNodeIndex);
break;
default:
throw new Error("Unable to determine the type of mutate operation for \"" + opCode + "\"");
}
}
}
setIsNotParent();
return visitedNodes;
}
function readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, viewData, bypassCheckBit) {
if (bypassCheckBit === void 0) { bypassCheckBit = false; }
var caseCreated = false;
for (var i = 0; i < updateOpCodes.length; i++) {
// bit code to check if we should apply the next update
var checkBit = updateOpCodes[i];
// Number of opCodes to skip until next set of update codes
var skipCodes = updateOpCodes[++i];
if (bypassCheckBit || (checkBit & changeMask)) {
// The value has been updated since last checked
var value = '';
for (var j = i + 1; j <= (i + skipCodes); j++) {
var opCode = updateOpCodes[j];
if (typeof opCode == 'string') {
value += opCode;
}
else if (typeof opCode == 'number') {
if (opCode < 0) {
// It's a binding index whose value is negative
value += renderStringify(viewData[bindingsStartIndex - opCode]);
}
else {
var nodeIndex = opCode >>> 2 /* SHIFT_REF */;
var tIcuIndex = void 0;
var tIcu = void 0;
var icuTNode = void 0;
switch (opCode & 3 /* MASK_OPCODE */) {
case 1 /* Attr */:
var propName = updateOpCodes[++j];
var sanitizeFn = updateOpCodes[++j];
elementPropertyInternal(nodeIndex, propName, value, sanitizeFn);
break;
case 0 /* Text */:
textBindingInternal(viewData, nodeIndex, value);
break;
case 2 /* IcuSwitch */:
tIcuIndex = updateOpCodes[++j];
tIcu = icus[tIcuIndex];
icuTNode = getTNode(nodeIndex, viewData);
// If there is an active case, delete the old nodes
if (icuTNode.activeCaseIndex !== null) {
var removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
for (var k = 0; k < removeCodes.length; k++) {
var removeOpCode = removeCodes[k];
switch (removeOpCode & 7 /* MASK_OPCODE */) {
case 3 /* Remove */:
var nodeIndex_1 = removeOpCode >>> 3 /* SHIFT_REF */;
removeNode(nodeIndex_1, viewData);
break;
case 6 /* RemoveNestedIcu */:
var nestedIcuNodeIndex = removeCodes[k + 1] >>> 3 /* SHIFT_REF */;
var nestedIcuTNode = getTNode(nestedIcuNodeIndex, viewData);
var activeIndex = nestedIcuTNode.activeCaseIndex;
if (activeIndex !== null) {
var nestedIcuTIndex = removeOpCode >>> 3 /* SHIFT_REF */;
var nestedTIcu = icus[nestedIcuTIndex];
addAllToArray(nestedTIcu.remove[activeIndex], removeCodes);
}
break;
}
}
}
// Update the active caseIndex
var caseIndex = getCaseIndex(tIcu, value);
icuTNode.activeCaseIndex = caseIndex !== -1 ? caseIndex : null;
// Add the nodes for the new case
readCreateOpCodes(-1, tIcu.create[caseIndex], icus, viewData);
caseCreated = true;
break;
case 3 /* IcuUpdate */:
tIcuIndex = updateOpCodes[++j];
tIcu = icus[tIcuIndex];
icuTNode = getTNode(nodeIndex, viewData);
readUpdateOpCodes(tIcu.update[icuTNode.activeCaseIndex], icus, bindingsStartIndex, changeMask, viewData, caseCreated);
break;
}
}
}
}
}
i += skipCodes;
}
}
function removeNode(index, viewData) {
var removedPhTNode = getTNode(index, viewData);
var removedPhRNode = getNativeByIndex(index, viewData);
if (removedPhRNode) {
nativeRemoveNode(viewData[RENDERER], removedPhRNode);
}
var slotValue = ɵɵload(index);
if (isLContainer(slotValue)) {
var lContainer = slotValue;
if (removedPhTNode.type !== 0 /* Container */) {
nativeRemoveNode(viewData[RENDERER], lContainer[NATIVE]);
}
}
// Define this node as detached so that we don't risk projecting it
removedPhTNode.flags |= 32 /* isDetached */;
ngDevMode && ngDevMode.rendererRemoveNode++;
}
/**
*
* Use this instruction to create a translation block that doesn't contain any placeholder.
* It calls both {@link i18nStart} and {@link i18nEnd} in one instruction.
*
* The translation `message` is the value which is locale specific. The translation string may
* contain placeholders which associate inner elements and sub-templates within the translation.
*
* The translation `message` placeholders are:
* - `�{index}(:{block})�`: *Binding Placeholder*: Marks a location where an expression will be
* interpolated into. The placeholder `index` points to the expression binding index. An optional
* `block` that matches the sub-template in which it was declared.
* - `�#{index}(:{block})�`/`�/#{index}(:{block})�`: *Element Placeholder*: Marks the beginning
* and end of DOM element that were embedded in the original translation block. The placeholder
* `index` points to the element index in the template instructions set. An optional `block` that
* matches the sub-template in which it was declared.
* - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be
* split up and translated separately in each angular template function. The `index` points to the
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
*
* @param index A unique index of the translation in the static block.
* @param message The translation message.
* @param subTemplateIndex Optional sub-template index in the `message`.
*
* @codeGenApi
*/
export function ɵɵi18n(index, message, subTemplateIndex) {
ɵɵi18nStart(index, message, subTemplateIndex);
ɵɵi18nEnd();
}
/**
* Marks a list of attributes as translatable.
*
* @param index A unique index in the static block
* @param values
*
* @codeGenApi
*/
export function ɵɵi18nAttributes(index, values) {
var tView = getLView()[TVIEW];
ngDevMode && assertDefined(tView, "tView should be defined");
i18nAttributesFirstPass(tView, index, values);
}
/**
* See `i18nAttributes` above.
*/
function i18nAttributesFirstPass(tView, index, values) {
var previousElement = getPreviousOrParentTNode();
var previousElementIndex = previousElement.index - HEADER_OFFSET;
var updateOpCodes = [];
for (var i = 0; i < values.length; i += 2) {
var attrName = values[i];
var message = values[i + 1];
var parts = message.split(ICU_REGEXP);
for (var j = 0; j < parts.length; j++) {
var value = parts[j];
if (j & 1) {
// Odd indexes are ICU expressions
// TODO(ocombe): support ICU expressions in attributes
throw new Error('ICU expressions are not yet supported in attributes');
}
else if (value !== '') {
// Even indexes are text (including bindings)
var hasBinding = !!value.match(BINDING_REGEXP);
if (hasBinding) {
if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) {
addAllToArray(generateBindingUpdateOpCodes(value, previousElementIndex, attrName), updateOpCodes);
}
}
else {
var lView = getLView();
elementAttributeInternal(previousElementIndex, attrName, value, lView);
// Check if that attribute is a directive input
var tNode = getTNode(previousElementIndex, lView);
var dataValue = tNode.inputs && tNode.inputs[attrName];
if (dataValue) {
setInputsForProperty(lView, dataValue, value);
}
}
}
}
}
if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) {
tView.data[index + HEADER_OFFSET] = updateOpCodes;
}
}
var changeMask = 0;
var shiftsCounter = 0;
/**
* Stores the values of the bindings during each update cycle in order to determine if we need to
* update the translated nodes.
*
* @param value The binding's value
* @returns This function returns itself so that it may be chained
* (e.g. `i18nExp(ctx.name)(ctx.title)`)
*
* @codeGenApi
*/
export function ɵɵi18nExp(value) {
var lView = getLView();
var expression = bind(lView, value);
if (expression !== NO_CHANGE) {
changeMask = changeMask | (1 << shiftsCounter);
}
shiftsCounter++;
return ɵɵi18nExp;
}
/**
* Updates a translation block or an i18n attribute when the bindings have changed.
*
* @param index Index of either {@link i18nStart} (translation block) or {@link i18nAttributes}
* (i18n attribute) on which it should update the content.
*
* @codeGenApi
*/
export function ɵɵi18nApply(index) {
if (shiftsCounter) {
var lView = getLView();
var tView = lView[TVIEW];
ngDevMode && assertDefined(tView, "tView should be defined");
var tI18n = tView.data[index + HEADER_OFFSET];
var updateOpCodes = void 0;
var icus = null;
if (Array.isArray(tI18n)) {
updateOpCodes = tI18n;
}
else {
updateOpCodes = tI18n.update;
icus = tI18n.icus;
}
var bindingsStartIndex = lView[BINDING_INDEX] - shiftsCounter - 1;
readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, lView);
// Reset changeMask & maskBit to default for the next update cycle
changeMask = 0;
shiftsCounter = 0;
}
}
/**
* Returns the index of the current case of an ICU expression depending on the main binding value
*
* @param icuExpression
* @param bindingValue The value of the main binding used by this ICU expression
*/
function getCaseIndex(icuExpression, bindingValue) {
var index = icuExpression.cases.indexOf(bindingValue);
if (index === -1) {
switch (icuExpression.type) {
case 1 /* plural */: {
var resolvedCase = getPluralCase(bindingValue, getLocaleId());
index = icuExpression.cases.indexOf(resolvedCase);
if (index === -1 && resolvedCase !== 'other') {
index = icuExpression.cases.indexOf('other');
}
break;
}
case 0 /* select */: {
index = icuExpression.cases.indexOf('other');
break;
}
}
}
return index;
}
/**
* Generate the OpCodes for ICU expressions.
*
* @param tIcus
* @param icuExpression
* @param startIndex
* @param expandoStartIndex
*/
function icuStart(tIcus, icuExpression, startIndex, expandoStartIndex) {
var createCodes = [];
var removeCodes = [];
var updateCodes = [];
var vars = [];
var childIcus = [];
for (var i = 0; i < icuExpression.values.length; i++) {
// Each value is an array of strings & other ICU expressions
var valueArr = icuExpression.values[i];
var nestedIcus = [];
for (var j = 0; j < valueArr.length; j++) {
var value = valueArr[j];
if (typeof value !== 'string') {
// It is an nested ICU expression
var icuIndex = nestedIcus.push(value) - 1;
// Replace nested ICU expression by a comment node
valueArr[j] = "<!--\uFFFD" + icuIndex + "\uFFFD-->";
}
}
var icuCase = parseIcuCase(valueArr.join(''), startIndex, nestedIcus, tIcus, expandoStartIndex);
createCodes.push(icuCase.create);
removeCodes.push(icuCase.remove);
updateCodes.push(icuCase.update);
vars.push(icuCase.vars);
childIcus.push(icuCase.childIcus);
}
var tIcu = {
type: icuExpression.type,
vars: vars,
childIcus: childIcus,
cases: icuExpression.cases,
create: createCodes,
remove: removeCodes,
update: updateCodes
};
tIcus.push(tIcu);
// Adding the maximum possible of vars needed (based on the cases with the most vars)
i18nVarsCount += Math.max.apply(Math, tslib_1.__spread(vars));
}
/**
* Transforms a string template into an HTML template and a list of instructions used to update
* attributes or nodes that contain bindings.
*
* @param unsafeHtml The string to parse
* @param parentIndex
* @param nestedIcus
* @param tIcus
* @param expandoStartIndex
*/
function parseIcuCase(unsafeHtml, parentIndex, nestedIcus, tIcus, expandoStartIndex) {
var inertBodyHelper = new InertBodyHelper(document);
var inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
if (!inertBodyElement) {
throw new Error('Unable to generate inert body element');
}
var wrapper = getTemplateContent(inertBodyElement) || inertBodyElement;
var opCodes = { vars: 0, childIcus: [], create: [], remove: [], update: [] };
parseNodes(wrapper.firstChild, opCodes, parentIndex, nestedIcus, tIcus, expandoStartIndex);
return opCodes;
}
var NESTED_ICU = /�(\d+)�/;
/**
* Parses a node, its children and its siblings, and generates the mutate & update OpCodes.
*
* @param currentNode The first node to parse
* @param icuCase The data for the ICU expression case that contains those nodes
* @param parentIndex Index of the current node's parent
* @param nestedIcus Data for the nested ICU expressions that this case contains
* @param tIcus Data for all ICU expressions of the current message
* @param expandoStartIndex Expando start index for the current ICU expression
*/
function parseNodes(currentNode, icuCase, parentIndex, nestedIcus, tIcus, expandoStartIndex) {
if (currentNode) {
var nestedIcusToCreate = [];
while (currentNode) {
var nextNode = currentNode.nextSibling;
var newIndex = expandoStartIndex + ++icuCase.vars;
switch (currentNode.nodeType) {
case Node.ELEMENT_NODE:
var element = currentNode;
var tagName = element.tagName.toLowerCase();
if (!VALID_ELEMENTS.hasOwnProperty(tagName)) {
// This isn't a valid element, we won't create an element for it
icuCase.vars--;
}
else {
icuCase.create.push(ELEMENT_MARKER, tagName, newIndex, parentIndex << 17 /* SHIFT_PARENT */ | 1 /* AppendChild */);
var elAttrs = element.attributes;
for (var i = 0; i < elAttrs.length; i++) {
var attr = elAttrs.item(i);
var lowerAttrName = attr.name.toLowerCase();
var hasBinding_1 = !!attr.value.match(BINDING_REGEXP);
// we assume the input string is safe, unless it's using a binding
if (hasBinding_1) {
if (VALID_ATTRS.hasOwnProperty(lowerAttrName)) {
if (URI_ATTRS[lowerAttrName]) {
addAllToArray(generateBindingUpdateOpCodes(attr.value, newIndex, attr.name, _sanitizeUrl), icuCase.update);
}
else if (SRCSET_ATTRS[lowerAttrName]) {
addAllToArray(generateBindingUpdateOpCodes(attr.value, newIndex, attr.name, sanitizeSrcset), icuCase.update);
}
else {
addAllToArray(generateBindingUpdateOpCodes(attr.value, newIndex, attr.name), icuCase.update);
}
}
else {
ngDevMode &&
console.warn("WARNING: ignoring unsafe attribute value " + lowerAttrName + " on element " + tagName + " (see http://g.co/ng/security#xss)");
}
}
else {
icuCase.create.push(newIndex << 3 /* SHIFT_REF */ | 4 /* Attr */, attr.name, attr.value);
}
}
// Parse the children of this node (if any)
parseNodes(currentNode.firstChild, icuCase, newIndex, nestedIcus, tIcus, expandoStartIndex);
// Remove the parent node after the children
icuCase.remove.push(newIndex << 3 /* SHIFT_REF */ | 3 /* Remove */);
}
break;
case Node.TEXT_NODE:
var value = currentNode.textContent || '';
var hasBinding = value.match(BINDING_REGEXP);
icuCase.create.push(hasBinding ? '' : value, newIndex, parentIndex << 17 /* SHIFT_PARENT */ | 1 /* AppendChild */);
icuCase.remove.push(newIndex << 3 /* SHIFT_REF */ | 3 /* Remove */);
if (hasBinding) {
addAllToArray(generateBindingUpdateOpCodes(value, newI