PHP Classes

File: public/js/tinymce/src/core/src/main/js/html/DomParser.js

Recommend this page to a friend!
  Classes of Abed Nego Ragil Putra   GoLavaCMS   public/js/tinymce/src/core/src/main/js/html/DomParser.js   Download  
File: public/js/tinymce/src/core/src/main/js/html/DomParser.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: GoLavaCMS
Publish content on Web pages with SEO support
Author: By
Last change:
Date: 6 years ago
Size: 31,804 bytes
 

Contents

Class file image Download
/** * DomParser.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make * sure that the node tree is valid according to the specified schema. * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p> * * @example * var parser = new tinymce.html.DomParser({validate: true}, schema); * var rootNode = parser.parse('<h1>content</h1>'); * * @class tinymce.html.DomParser * @version 3.4 */ define( 'tinymce.core.html.DomParser', [ 'tinymce.core.html.LegacyFilter', 'tinymce.core.html.Node', 'tinymce.core.html.SaxParser', 'tinymce.core.html.Schema', 'tinymce.core.util.Tools' ], function (LegacyFilter, Node, SaxParser, Schema, Tools) { var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend; var paddEmptyNode = function (settings, args, blockElements, node) { var brPreferred = settings.padd_empty_with_br || args.insert; if (brPreferred && blockElements[node.name]) { node.empty().append(new Node('br', '1')).shortEnded = true; } else { node.empty().append(new Node('#text', '3')).value = '\u00a0'; } }; var isPaddedWithNbsp = function (node) { return hasOnlyChild(node, '#text') && node.firstChild.value === '\u00a0'; }; var hasOnlyChild = function (node, name) { return node && node.firstChild && node.firstChild === node.lastChild && node.firstChild.name === name; }; var isPadded = function (schema, node) { var rule = schema.getElementRule(node.name); return rule && rule.paddEmpty; }; var isEmpty = function (schema, nonEmptyElements, whitespaceElements, node) { return node.isEmpty(nonEmptyElements, whitespaceElements, function (node) { return isPadded(schema, node); }); }; /** * Constructs a new DomParser instance. * * @constructor * @method DomParser * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. */ return function (settings, schema) { var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; settings = settings || {}; settings.validate = "validate" in settings ? settings.validate : true; settings.root_name = settings.root_name || 'body'; self.schema = schema = schema || new Schema(); var fixInvalidChildren = function (nodes) { var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i; var nonEmptyElements, whitespaceElements, nonSplitableElements, textBlockElements, specialElements, sibling, nextNode; nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table'); nonEmptyElements = schema.getNonEmptyElements(); whitespaceElements = schema.getWhiteSpaceElements(); textBlockElements = schema.getTextBlockElements(); specialElements = schema.getSpecialElements(); for (ni = 0; ni < nodes.length; ni++) { node = nodes[ni]; // Already removed or fixed if (!node.parent || node.fixed) { continue; } // If the invalid element is a text block and the text block is within a parent LI element // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office if (textBlockElements[node.name] && node.parent.name == 'li') { // Move sibling text blocks after LI element sibling = node.next; while (sibling) { if (textBlockElements[sibling.name]) { sibling.name = 'li'; sibling.fixed = true; node.parent.insert(sibling, node.parent); } else { break; } sibling = sibling.next; } // Unwrap current text block node.unwrap(node); continue; } // Get list of all parent nodes until we find a valid parent to stick the child into parents = [node]; for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) { parents.push(parent); } // Found a suitable parent if (parent && parents.length > 1) { // Reverse the array since it makes looping easier parents.reverse(); // Clone the related parent and insert that after the moved node newParent = currentNode = self.filterNode(parents[0].clone()); // Start cloning and moving children on the left side of the target node for (i = 0; i < parents.length - 1; i++) { if (schema.isValidChild(currentNode.name, parents[i].name)) { tempNode = self.filterNode(parents[i].clone()); currentNode.append(tempNode); } else { tempNode = currentNode; } for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) { nextNode = childNode.next; tempNode.append(childNode); childNode = nextNode; } currentNode = tempNode; } if (!isEmpty(schema, nonEmptyElements, whitespaceElements, newParent)) { parent.insert(newParent, parents[0], true); parent.insert(node, newParent); } else { parent.insert(node, parents[0], true); } // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> parent = parents[0]; if (isEmpty(schema, nonEmptyElements, whitespaceElements, parent) || hasOnlyChild(parent, 'br')) { parent.empty().remove(); } } else if (node.parent) { // If it's an LI try to find a UL/OL for it or wrap it if (node.name === 'li') { sibling = node.prev; if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { sibling.append(node); continue; } sibling = node.next; if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { sibling.insert(node, sibling.firstChild, true); continue; } node.wrap(self.filterNode(new Node('ul', 1))); continue; } // Try wrapping the element in a DIV if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { node.wrap(self.filterNode(new Node('div', 1))); } else { // We failed wrapping it, then remove or unwrap it if (specialElements[node.name]) { node.empty().remove(); } else { node.unwrap(); } } } } }; /** * Runs the specified node though the element and attributes filters. * * @method filterNode * @param {tinymce.html.Node} Node the node to run filters on. * @return {tinymce.html.Node} The passed in node. */ self.filterNode = function (node) { var i, name, list; // Run element filters if (name in nodeFilters) { list = matchedNodes[name]; if (list) { list.push(node); } else { matchedNodes[name] = [node]; } } // Run attribute filters i = attributeFilters.length; while (i--) { name = attributeFilters[i].name; if (name in node.attributes.map) { list = matchedAttributes[name]; if (list) { list.push(node); } else { matchedAttributes[name] = [node]; } } } return node; }; /** * Adds a node filter function to the parser, the parser will collect the specified nodes by name * and then execute the callback ones it has finished parsing the document. * * @example * parser.addNodeFilter('p,h1', function(nodes, name) { * for (var i = 0; i < nodes.length; i++) { * console.log(nodes[i].name); * } * }); * @method addNodeFilter * @method {String} name Comma separated list of nodes to collect. * @param {function} callback Callback function to execute once it has collected nodes. */ self.addNodeFilter = function (name, callback) { each(explode(name), function (name) { var list = nodeFilters[name]; if (!list) { nodeFilters[name] = list = []; } list.push(callback); }); }; /** * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes * and then execute the callback ones it has finished parsing the document. * * @example * parser.addAttributeFilter('src,href', function(nodes, name) { * for (var i = 0; i < nodes.length; i++) { * console.log(nodes[i].name); * } * }); * @method addAttributeFilter * @method {String} name Comma separated list of nodes to collect. * @param {function} callback Callback function to execute once it has collected nodes. */ self.addAttributeFilter = function (name, callback) { each(explode(name), function (name) { var i; for (i = 0; i < attributeFilters.length; i++) { if (attributeFilters[i].name === name) { attributeFilters[i].callbacks.push(callback); return; } } attributeFilters.push({ name: name, callbacks: [callback] }); }); }; /** * Parses the specified HTML string into a DOM like node tree and returns the result. * * @example * var rootNode = new DomParser({...}).parse('<b>text</b>'); * @method parse * @param {String} html Html string to sax parse. * @param {Object} args Optional args object that gets passed to all filter functions. * @return {tinymce.html.Node} Root node containing the tree. */ self.parse = function (html, args) { var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate; var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement; var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements; var children, nonEmptyElements, rootBlockName; args = args || {}; matchedNodes = {}; matchedAttributes = {}; blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); nonEmptyElements = schema.getNonEmptyElements(); children = schema.children; validate = settings.validate; rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; whiteSpaceElements = schema.getWhiteSpaceElements(); startWhiteSpaceRegExp = /^[ \t\r\n]+/; endWhiteSpaceRegExp = /[ \t\r\n]+$/; allWhiteSpaceRegExp = /[ \t\r\n]+/g; isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; var addRootBlocks = function () { var node = rootNode.firstChild, next, rootBlockNode; // Removes whitespace at beginning and end of block so: // <p> x </p> -> <p>x</p> var trim = function (rootBlockNode) { if (rootBlockNode) { node = rootBlockNode.firstChild; if (node && node.type == 3) { node.value = node.value.replace(startWhiteSpaceRegExp, ''); } node = rootBlockNode.lastChild; if (node && node.type == 3) { node.value = node.value.replace(endWhiteSpaceRegExp, ''); } } }; // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) { return; } while (node) { next = node.next; if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { if (!rootBlockNode) { // Create a new root block element rootBlockNode = createNode(rootBlockName, 1); rootBlockNode.attr(settings.forced_root_block_attrs); rootNode.insert(rootBlockNode, node); rootBlockNode.append(node); } else { rootBlockNode.append(node); } } else { trim(rootBlockNode); rootBlockNode = null; } node = next; } trim(rootBlockNode); }; var createNode = function (name, type) { var node = new Node(name, type), list; if (name in nodeFilters) { list = matchedNodes[name]; if (list) { list.push(node); } else { matchedNodes[name] = [node]; } } return node; }; var removeWhitespaceBefore = function (node) { var textNode, textNodeNext, textVal, sibling, blockElements = schema.getBlockElements(); for (textNode = node.prev; textNode && textNode.type === 3;) { textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); // Found a text node with non whitespace then trim that and break if (textVal.length > 0) { textNode.value = textVal; return; } textNodeNext = textNode.next; // Fix for bug #7543 where bogus nodes would produce empty // text nodes and these would be removed if a nested list was before it if (textNodeNext) { if (textNodeNext.type == 3 && textNodeNext.value.length) { textNode = textNode.prev; continue; } if (!blockElements[textNodeNext.name] && textNodeNext.name != 'script' && textNodeNext.name != 'style') { textNode = textNode.prev; continue; } } sibling = textNode.prev; textNode.remove(); textNode = sibling; } }; var cloneAndExcludeBlocks = function (input) { var name, output = {}; for (name in input) { if (name !== 'li' && name != 'p') { output[name] = input[name]; } } return output; }; parser = new SaxParser({ validate: validate, allow_script_urls: settings.allow_script_urls, allow_conditional_comments: settings.allow_conditional_comments, // Exclude P and LI from DOM parsing since it's treated better by the DOM parser self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), cdata: function (text) { node.append(createNode('#cdata', 4)).value = text; }, text: function (text, raw) { var textNode; // Trim all redundant whitespace on non white space elements if (!isInWhiteSpacePreservedElement) { text = text.replace(allWhiteSpaceRegExp, ' '); if (node.lastChild && blockElements[node.lastChild.name]) { text = text.replace(startWhiteSpaceRegExp, ''); } } // Do we need to create the node if (text.length !== 0) { textNode = createNode('#text', 3); textNode.raw = !!raw; node.append(textNode).value = text; } }, comment: function (text) { node.append(createNode('#comment', 8)).value = text; }, pi: function (name, text) { node.append(createNode(name, 7)).value = text; removeWhitespaceBefore(node); }, doctype: function (text) { var newNode; newNode = node.append(createNode('#doctype', 10)); newNode.value = text; removeWhitespaceBefore(node); }, start: function (name, attrs, empty) { var newNode, attrFiltersLen, elementRule, attrName, parent; elementRule = validate ? schema.getElementRule(name) : {}; if (elementRule) { newNode = createNode(elementRule.outputName || name, 1); newNode.attributes = attrs; newNode.shortEnded = empty; node.append(newNode); // Check if node is valid child of the parent node is the child is // unknown we don't collect it since it's probably a custom element parent = children[node.name]; if (parent && children[newNode.name] && !parent[newNode.name]) { invalidChildren.push(newNode); } attrFiltersLen = attributeFilters.length; while (attrFiltersLen--) { attrName = attributeFilters[attrFiltersLen].name; if (attrName in attrs.map) { list = matchedAttributes[attrName]; if (list) { list.push(newNode); } else { matchedAttributes[attrName] = [newNode]; } } } // Trim whitespace before block if (blockElements[name]) { removeWhitespaceBefore(newNode); } // Change current node if the element wasn't empty i.e not <br /> or <img /> if (!empty) { node = newNode; } // Check if we are inside a whitespace preserved element if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { isInWhiteSpacePreservedElement = true; } } }, end: function (name) { var textNode, elementRule, text, sibling, tempNode; elementRule = validate ? schema.getElementRule(name) : {}; if (elementRule) { if (blockElements[name]) { if (!isInWhiteSpacePreservedElement) { // Trim whitespace of the first node in a block textNode = node.firstChild; if (textNode && textNode.type === 3) { text = textNode.value.replace(startWhiteSpaceRegExp, ''); // Any characters left after trim or should we remove it if (text.length > 0) { textNode.value = text; textNode = textNode.next; } else { sibling = textNode.next; textNode.remove(); textNode = sibling; // Remove any pure whitespace siblings while (textNode && textNode.type === 3) { text = textNode.value; sibling = textNode.next; if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { textNode.remove(); textNode = sibling; } textNode = sibling; } } } // Trim whitespace of the last node in a block textNode = node.lastChild; if (textNode && textNode.type === 3) { text = textNode.value.replace(endWhiteSpaceRegExp, ''); // Any characters left after trim or should we remove it if (text.length > 0) { textNode.value = text; textNode = textNode.prev; } else { sibling = textNode.prev; textNode.remove(); textNode = sibling; // Remove any pure whitespace siblings while (textNode && textNode.type === 3) { text = textNode.value; sibling = textNode.prev; if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { textNode.remove(); textNode = sibling; } textNode = sibling; } } } } // Trim start white space // Removed due to: #5424 /*textNode = node.prev; if (textNode && textNode.type === 3) { text = textNode.value.replace(startWhiteSpaceRegExp, ''); if (text.length > 0) textNode.value = text; else textNode.remove(); }*/ } // Check if we exited a whitespace preserved element if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { isInWhiteSpacePreservedElement = false; } if (elementRule.removeEmpty && isEmpty(schema, nonEmptyElements, whiteSpaceElements, node)) { // Leave nodes that have a name like <a name="name"> if (!node.attributes.map.name && !node.attributes.map.id) { tempNode = node.parent; if (blockElements[node.name]) { node.empty().remove(); } else { node.unwrap(); } node = tempNode; return; } } if (elementRule.paddEmpty && (isPaddedWithNbsp(node) || isEmpty(schema, nonEmptyElements, whiteSpaceElements, node))) { paddEmptyNode(settings, args, blockElements, node); } node = node.parent; } } }, schema); rootNode = node = new Node(args.context || settings.root_name, 11); parser.parse(html); // Fix invalid children or report invalid children in a contextual parsing if (validate && invalidChildren.length) { if (!args.context) { fixInvalidChildren(invalidChildren); } else { args.invalid = true; } } // Wrap nodes in the root into block elements if the root is body if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) { addRootBlocks(); } // Run filters only when the contents is valid if (!args.invalid) { // Run node filters for (name in matchedNodes) { list = nodeFilters[name]; nodes = matchedNodes[name]; // Remove already removed children fi = nodes.length; while (fi--) { if (!nodes[fi].parent) { nodes.splice(fi, 1); } } for (i = 0, l = list.length; i < l; i++) { list[i](nodes, name, args); } } // Run attribute filters for (i = 0, l = attributeFilters.length; i < l; i++) { list = attributeFilters[i]; if (list.name in matchedAttributes) { nodes = matchedAttributes[list.name]; // Remove already removed children fi = nodes.length; while (fi--) { if (!nodes[fi].parent) { nodes.splice(fi, 1); } } for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) { list.callbacks[fi](nodes, list.name, args); } } } } return rootNode; }; // Remove <br> at end of block elements Gecko and WebKit injects BR elements to // make it possible to place the caret inside empty blocks. This logic tries to remove // these elements and keep br elements that where intended to be there intact if (settings.remove_trailing_brs) { self.addNodeFilter('br', function (nodes, _, args) { var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements()); var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; var whiteSpaceElements = schema.getNonEmptyElements(); var elementRule, textNode; // Remove brs from body element as well blockElements.body = 1; // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> for (i = 0; i < l; i++) { node = nodes[i]; parent = node.parent; if (blockElements[node.parent.name] && node === parent.lastChild) { // Loop all nodes to the left of the current node and check for other BR elements // excluding bookmarks since they are invisible prev = node.prev; while (prev) { prevName = prev.name; // Ignore bookmarks if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { // Found a non BR element if (prevName !== "br") { break; } // Found another br it's a <br><br> structure then don't remove anything if (prevName === 'br') { node = null; break; } } prev = prev.prev; } if (node) { node.remove(); // Is the parent to be considered empty after we removed the BR if (isEmpty(schema, nonEmptyElements, whiteSpaceElements, parent)) { elementRule = schema.getElementRule(parent.name); // Remove or padd the element depending on schema rule if (elementRule) { if (elementRule.removeEmpty) { parent.remove(); } else if (elementRule.paddEmpty) { paddEmptyNode(settings, args, blockElements, parent); } } } } } else { // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> // so they become <p><b><i>&nbsp;</i></b></p> lastParent = node; while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) { lastParent = parent; if (blockElements[parent.name]) { break; } parent = parent.parent; } if (lastParent === parent && settings.padd_empty_with_br !== true) { textNode = new Node('#text', 3); textNode.value = '\u00a0'; node.replace(textNode); } } } }); } self.addAttributeFilter('href', function (nodes) { var i = nodes.length, node; var appendRel = function (rel) { var parts = rel.split(' ').filter(function (p) { return p.length > 0; }); return parts.concat(['noopener']).sort().join(' '); }; var addNoOpener = function (rel) { var newRel = rel ? Tools.trim(rel) : ''; if (!/\b(noopener)\b/g.test(newRel)) { return appendRel(newRel); } else { return newRel; } }; if (!settings.allow_unsafe_link_target) { while (i--) { node = nodes[i]; if (node.name === 'a' && node.attr('target') === '_blank') { node.attr('rel', addNoOpener(node.attr('rel'))); } } } }); // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. if (!settings.allow_html_in_named_anchor) { self.addAttributeFilter('id,name', function (nodes) { var i = nodes.length, sibling, prevSibling, parent, node; while (i--) { node = nodes[i]; if (node.name === 'a' && node.firstChild && !node.attr('href')) { parent = node.parent; // Move children after current node sibling = node.lastChild; do { prevSibling = sibling.prev; parent.insert(sibling, node); sibling = prevSibling; } while (sibling); } } }); } if (settings.fix_list_elements) { self.addNodeFilter('ul,ol', function (nodes) { var i = nodes.length, node, parentNode; while (i--) { node = nodes[i]; parentNode = node.parent; if (parentNode.name === 'ul' || parentNode.name === 'ol') { if (node.prev && node.prev.name === 'li') { node.prev.append(node); } else { var li = new Node('li', 1); li.attr('style', 'list-style-type: none'); node.wrap(li); } } } }); } if (settings.validate && schema.getValidClasses()) { self.addAttributeFilter('class', function (nodes) { var i = nodes.length, node, classList, ci, className, classValue; var validClasses = schema.getValidClasses(), validClassesMap, valid; while (i--) { node = nodes[i]; classList = node.attr('class').split(' '); classValue = ''; for (ci = 0; ci < classList.length; ci++) { className = classList[ci]; valid = false; validClassesMap = validClasses['*']; if (validClassesMap && validClassesMap[className]) { valid = true; } validClassesMap = validClasses[node.name]; if (!valid && validClassesMap && validClassesMap[className]) { valid = true; } if (valid) { if (classValue) { classValue += ' '; } classValue += className; } } if (!classValue.length) { classValue = null; } node.attr('class', classValue); } }); } LegacyFilter.register(this, settings); }; } );