UNPKG

angular-xml-editor

Version:
1,265 lines (1,255 loc) 388 kB
import { Subject } from 'rxjs'; import { Injectable, Inject, Component, Input, NgModule, EventEmitter, ViewEncapsulation, forwardRef, Renderer2, ViewChild, Output, defineInjectable, inject } from '@angular/core'; import { DOCUMENT, CommonModule } from '@angular/common'; import { NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class XmlEditorDebugger { constructor() { this.actualCursorPosChanged$ = new Subject(); this.inputSelectionChanged$ = new Subject(); } /** * @param {?} pos * @return {?} */ setActualCursorPos(pos) { this.actualCursorPosChanged$.next(pos); } /** * @param {?} info * @return {?} */ setInputSelection(info) { this.inputSelectionChanged$.next(info); } /** * @return {?} */ ngOnDestroy() { this.actualCursorPosChanged$.complete(); this.inputSelectionChanged$.complete(); } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class XmlToolbox { /** * @param {?} node * @return {?} */ static IsTextOrCommentNode(node) { switch (node.nodeType) { case Node.TEXT_NODE: // text node case Node.COMMENT_NODE: return true; default: return false; } // return ((node.nodeType === System.Xml.XmlText) || (node.nodeType === System.Xml.XmlComment)); } /** * @param {?} node * @return {?} */ static GetNodeDebugContext(node) { if (node === null || node === undefined) { return 'NULL'; } if (node.nodeType === Node.TEXT_NODE) { if (node.textContent === ' ' || node.textContent === String.fromCharCode(160)) { return `TextContent!Parent=${XmlToolbox.GetNodeDebugContext(node.parentNode)}`; } else { return `TextContent='${node.textContent}'`; } } /** @type {?} */ const asElem = /** @type {?} */ (node); if (asElem === undefined) { return `localName: ${node.nodeName}, nodeType:${node.nodeType}`; } else { return asElem.outerHTML; } } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class Xml2htmlAttributeWorker { /** * @param {?} rules */ constructor(rules) { this.rules = rules; } /** * writes the given attributes to the html node in data-attribute-syntax * * @param {?} xmlElement * @param {?} htmlElement * @return {?} */ writeAllXmlAttributesToHtml(xmlElement, htmlElement) { /** @type {?} */ const attributes = xmlElement.attributes; for (let i = 0; i < attributes.length; ++i) { /** @type {?} */ const attr = attributes[i]; this.writeAttributeToHtml(htmlElement, attr.name, attr.value); } } /** * writes the given attribute value to the html node in data-attribute-syntax * * @param {?} htmlElement * @param {?} key * @param {?} value * @return {?} */ writeAttributeToHtml(htmlElement, key, value) { /** @type {?} */ const name = `${Xml2htmlAttributeWorker.DataAttribPraefix}${key}`; if (value && value.length > 0) { htmlElement.setAttribute(name, value); } else { htmlElement.removeAttribute(name); } this.updateAttributesDescriptionInHtmlElement(htmlElement); } /** * writes the given attribute value from the html node data-attribute-syntax * * @param {?} htmlElement * @param {?} key * @return {?} */ getAttributeValueFromHtml(htmlElement, key) { /** @type {?} */ const attributes = htmlElement.attributes; for (let i = 0; i < attributes.length; ++i) { if (attributes[i].name === `${Xml2htmlAttributeWorker.DataAttribPraefix}${key}`) { return attributes[i].value; } } return undefined; } /** * writes the given attribute value to the xml node (and gets it from the html node from data-attribute-syntax) * * @param {?} htmlElement * @param {?} xmlElement * @return {?} */ writeAllHtmlAttributesToXml(htmlElement, xmlElement) { /** @type {?} */ const attributes = htmlElement.attributes; for (let i = 0; i < attributes.length; ++i) { /** @type {?} */ const attr = attributes[i]; if (attr.name.startsWith(Xml2htmlAttributeWorker.DataAttribPraefix)) { xmlElement.setAttribute(attr.name.substr(Xml2htmlAttributeWorker.DataAttribPraefix.length), attr.value); } } } /** * updates the 'for human eyes' only description of the attributes inside a html node * * @param {?} htmlElement * @return {?} */ updateAttributesDescriptionInHtmlElement(htmlElement) { /** @type {?} */ const attributes = htmlElement.attributes; /** @type {?} */ let allAttributes = ''; for (let i = 0; i < attributes.length; ++i) { /** @type {?} */ const attr = attributes[i]; if (attr.name.startsWith(Xml2htmlAttributeWorker.DataAttribPraefix)) { allAttributes = `${allAttributes} ${attr.name.substr(Xml2htmlAttributeWorker.DataAttribPraefix.length)}="${attr.value}"`; } } if (allAttributes.length > 0) { htmlElement.setAttribute('data-attributes', allAttributes); } else { htmlElement.removeAttribute('data-attributes'); } } } Xml2htmlAttributeWorker.DataAttribPraefix = 'data-attribute-'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class Xml2html { /** * @param {?} rules */ constructor(rules) { this.rules = rules; this.attributeWorker = new Xml2htmlAttributeWorker(rules); } /** * @param {?} node * @return {?} */ static getTagNameFromNode(node) { /** @type {?} */ const elem = /** @type {?} */ (node); /** @type {?} */ let name = ''; if (elem) { name = elem.getAttribute(Xml2html.DataTagName); } if (name === undefined || name === null || name === '') { throw new Error(`node has no DataTagName attribute ${XmlToolbox.GetNodeDebugContext(node)}`); } return name; } /** * @param {?} node * @return {?} */ static isFloatingElement(node) { /** @type {?} */ const elem = /** @type {?} */ (node); if (elem && elem.classList) { return elem.classList.contains(Xml2html.FloatingTagClassName); } return false; } /** * @param {?} node * @return {?} */ static isNoClosingElement(node) { /** @type {?} */ const elem = /** @type {?} */ (node); if (elem && elem.classList) { return elem.classList.contains(Xml2html.NoClosingTagClassName); } return false; } /** * @param {?} xml * @return {?} */ ToHtml(xml) { if (xml === '' || xml === undefined || xml === null) { return ''; } // alert(xml); // remove newline / carriage return xml = xml.replace(/\n/g, ''); xml = xml.replace(/\r/g, ''); // remove whitespace (tabs) before tags xml = xml.replace(/[\t]+\</g, '<'); // remove whitespace between tags xml = xml.replace(/\>[\t]+\</g, '><'); // remove whitespace after tags xml = xml.replace(/\>[\t]+$/g, '>'); /** @type {?} */ const domparser = new DOMParser(); /** @type {?} */ const domdoc = domparser.parseFromString(xml, 'text/xml'); /** @type {?} */ const targetDocument = new Document(); if (domdoc.documentElement === null) { return ''; } /** @type {?} */ const body = this.GetHtmlElementFromXmlElement(domdoc.documentElement, targetDocument); if (body && body.nodeType === Node.ELEMENT_NODE) { /** @type {?} */ const html = (/** @type {?} */ (body)).outerHTML; return html; } else { throw new Error(`root node ${XmlToolbox.GetNodeDebugContext(body)} is no element`); } } /** * @param {?} ruleElem * @return {?} */ createNewElementHtml(ruleElem) { /** @type {?} */ const elem = this.createNewElementByName(ruleElem.tagName); return elem.outerHTML; } /** * @param {?} tagName * @return {?} */ createNewElementByName(tagName) { /** @type {?} */ const tempDoc = new Document(); /** @type {?} */ const newElement = tempDoc.createElement('div'); /** @type {?} */ const rule = this.rules.ruleElements.get(tagName); if (rule && rule.floating === true) { newElement.classList.add(Xml2html.FloatingTagClassName); } // visualize closing tag or not? if (rule && rule.empty === true) { newElement.classList.add(Xml2html.NoClosingTagClassName); } newElement.setAttribute(Xml2html.DataTagName, tagName); return newElement; } /** * @param {?} node * @param {?} targetDocument * @return {?} */ GetHtmlElementFromXmlElement(node, targetDocument) { switch (node.nodeType) { case Node.ELEMENT_NODE: // element node /** @type {?} */ const asElement = /** @type {?} */ (node); /** @type {?} */ const nodeName = asElement.nodeName; /** @type {?} */ let newElement = targetDocument.createElement('div'); /** @type {?} */ const rule = this.rules.ruleElements.get(nodeName); if (rule && rule.floating) { newElement.classList.add(Xml2html.FloatingTagClassName); } // visualize closing tag or not? if (rule && rule.empty) { newElement.classList.add(Xml2html.NoClosingTagClassName); } newElement.setAttribute(Xml2html.DataTagName, nodeName); /** @type {?} */ const children = asElement.childNodes; if (children.length === 0) { newElement.appendChild(targetDocument.createTextNode('')); // to prevent non closing tags } else { for (let i = 0; i < children.length; ++i) { /** @type {?} */ const childAsHtml = this.GetHtmlElementFromXmlElement(children[i], targetDocument); if (childAsHtml !== undefined) { newElement.appendChild(childAsHtml); } } } // insert attributes this.attributeWorker.writeAllXmlAttributesToHtml(asElement, newElement); return newElement; case Node.TEXT_NODE: // text node /** @type {?} */ const content = (/** @type {?} */ (node)).textContent; if (content.length > 0) { return targetDocument.createTextNode(content); } else { return undefined; } case Node.COMMENT_NODE: // create element node newElement = targetDocument.createElement('div'); newElement.classList.add(Xml2html.CommentTagClassName); newElement.setAttribute(Xml2html.DataTagName, 'comment'); newElement.textContent = (/** @type {?} */ (node)).textContent; return newElement; /* yet not implemented types */ case Node.PROCESSING_INSTRUCTION_NODE: console.warn(`nodeType "PROCESSING_INSTRUCTION_NODE" ${XmlToolbox.GetNodeDebugContext(node)} not implemented yet`); return undefined; case Node.DOCUMENT_NODE: throw new Error(`nodeType "Node.DOCUMENT_NODE" ${XmlToolbox.GetNodeDebugContext(node)} not implemented yet`); case Node.DOCUMENT_TYPE_NODE: throw new Error(`nodeType "Node.DOCUMENT_NODE" ${XmlToolbox.GetNodeDebugContext(node)} not implemented yet`); case Node.DOCUMENT_FRAGMENT_NODE: throw new Error(`nodeType "Node.DOCUMENT_FRAGMENT_NODE" ${XmlToolbox.GetNodeDebugContext(node)} not implemented yet`); /* Depreached node types */ case Node.ATTRIBUTE_NODE: throw new Error(`nodeType "Node.ATTRIBUTE_NODE" ${XmlToolbox.GetNodeDebugContext(node)} is depreached`); case Node.CDATA_SECTION_NODE: throw new Error(`nodeType "Node.CDATA_SECTION_NODE" ${XmlToolbox.GetNodeDebugContext(node)} is depreached`); case Node.ENTITY_REFERENCE_NODE: throw new Error(`nodeType "Node.ENTITY_REFERENCE_NODE" ${XmlToolbox.GetNodeDebugContext(node)} is depreached`); case Node.ENTITY_NODE: throw new Error(`nodeType "Node.ENTITY_NODE" ${XmlToolbox.GetNodeDebugContext(node)} is depreached`); case Node.NOTATION_NODE: throw new Error(`nodeType "Node.NOTATION_NODE" ${XmlToolbox.GetNodeDebugContext(node)} is depreached`); default: // tslint:disable-next-line:max-line-length throw new Error(`unknown xmlElement nodeType ${node.nodeType} in value: ${XmlToolbox.GetNodeDebugContext(node)}`); } } } Xml2html.DataTagName = 'data-tagname'; Xml2html.NoClosingTagClassName = 'xmlTagNoClosing'; Xml2html.FloatingTagClassName = 'floating'; Xml2html.CommentTagClassName = 'comment'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class DomDummyNodeManager { constructor() { } /** * @param {?} node * @return {?} */ static IsDummyNode(node) { /** @type {?} */ const asElem = /** @type {?} */ (node); if (asElem && node.nodeType === Node.ELEMENT_NODE) { return asElem.classList.contains(DomDummyNodeManager.DummyNodeClassName); } return false; } /** * @param {?} node * @return {?} */ static IsBeforeFirstTagDummyNode(node) { if (DomDummyNodeManager.IsDummyNode(node)) { /** @type {?} */ const asElem = /** @type {?} */ (node); if (asElem && node.nodeType === Node.ELEMENT_NODE) { return asElem.classList.contains(DomDummyNodeManager.DummyNodeBeforeFirstTagClassName); } return false; } else { return false; } } /** * @param {?} node * @return {?} */ static getPreviousSibling(node) { if (!node) { return undefined; } while (node) { node = node.previousSibling; if (node) { if (DomDummyNodeManager.IsDummyNode(node)) ; else { return node; } } } return undefined; } /** * @param {?} node * @return {?} */ static getNextSibling(node) { if (!node) { return undefined; } while (node) { node = node.nextSibling; if (node) { if (DomDummyNodeManager.IsDummyNode(node)) ; else { return node; } } } return undefined; } /** * @param {?} node * @return {?} */ static getChildNodeCount(node) { if (!node) { return 0; } /** @type {?} */ let count = 0; for (let i = 0; i < node.childNodes.length; i++) { if (!DomDummyNodeManager.IsDummyNode(node.childNodes[i])) { count++; } } return count; } /** * @param {?} node * @return {?} */ static getFirstChild(node) { if (!node) { return undefined; } /** @type {?} */ let first = /** @type {?} */ (node.firstChild); while (DomDummyNodeManager.IsDummyNode(first)) { first = first.nextSibling; } return first; } /** * @param {?} node * @return {?} */ static getLastChild(node) { if (!node) { return undefined; } /** @type {?} */ let last = /** @type {?} */ (node.lastChild); while (DomDummyNodeManager.IsDummyNode(last)) { last = last.previousSibling; } return last; } /** * @param {?} node * @param {?=} nodeDepth * @return {?} */ RemoveAllDummyNodes(node, nodeDepth = 0) { /** @type {?} */ let children = []; for (let i = 0; i < node.childNodes.length; ++i) { children.push(node.childNodes[i]); } // delete all dummy node children children.forEach(child => { if (DomDummyNodeManager.IsDummyNode(child)) { node.removeChild(child); } }); children = []; for (let i = 0; i < node.childNodes.length; ++i) { children.push(node.childNodes[i]); } // update child nodes children.forEach(child => { this.RemoveAllDummyNodes(child, nodeDepth + 1); }); } /** * @param {?} node * @param {?=} nodeDepth * @return {?} */ UpdateDummyNodes(node, nodeDepth = 0) { /** @type {?} */ const document = node.ownerDocument; /** @type {?} */ const asElement = /** @type {?} */ (node); /** @type {?} */ const children = []; for (let i = 0; i < node.childNodes.length; ++i) { children.push(node.childNodes[i]); } if (asElement && !DomDummyNodeManager.IsDummyNode(asElement) && !DomDummyNodeManager.IsDummyNode(asElement.parentElement)) { if (asElement.nodeType === Node.TEXT_NODE) { /** @type {?} */ const before = asElement.previousSibling; if (before.nodeType === Node.TEXT_NODE) ; else { if (!DomDummyNodeManager.IsDummyNode(before)) { asElement.parentNode.insertBefore(this.createDummyNode(document), asElement); } } } if (asElement.nodeType === Node.ELEMENT_NODE) { /** @type {?} */ let firstChild = node.firstChild; if (!firstChild) { // create dummy node in empty tag node.appendChild(this.createDummyNode(document)); firstChild = node.firstChild; } if (Xml2html.isNoClosingElement(node)) ; else { firstChild = node.firstChild; if (!firstChild) { // create dummy node in empty tag node.appendChild(this.createDummyNode(document)); firstChild = node.firstChild; } // Insert a dummy node before the first child if (!DomDummyNodeManager.IsDummyNode(firstChild)) { if (firstChild) { node.insertBefore(this.createDummyNode(document), firstChild); firstChild = node.firstChild; } else { node.appendChild(this.createDummyNode(document)); } } /** @type {?} */ let lastChild = node.lastChild; if (!DomDummyNodeManager.IsDummyNode(lastChild)) { node.appendChild(this.createDummyNode(document)); lastChild = node.lastChild; } // Insert a dummy node before the first (dummy child) but optical in front of the node (to show the cursor in front of the node) if (nodeDepth > 0) { if (DomDummyNodeManager.IsDummyNode(firstChild) && !DomDummyNodeManager.IsBeforeFirstTagDummyNode(firstChild)) { /** @type {?} */ const dummyNode = this.createDummyNode(document); dummyNode.classList.add(DomDummyNodeManager.DummyNodeBeforeFirstTagClassName); asElement.insertBefore(dummyNode, firstChild); } } } } } // update child nodes children.forEach(child => { this.UpdateDummyNodes(child, nodeDepth + 1); }); } /** * @param {?} targetDocument * @param {?=} content * @return {?} */ createDummyNode(targetDocument, content) { /** @type {?} */ const dummyNode = targetDocument.createElement('span'); /** @type {?} */ let dummy2TextNode; if (content === undefined) { dummy2TextNode = targetDocument.createTextNode(DomDummyNodeManager.DummyNodeContent); } else { dummy2TextNode = targetDocument.createTextNode(content); } dummyNode.classList.add(DomDummyNodeManager.DummyNodeClassName); dummyNode.appendChild(dummy2TextNode); return dummyNode; } } DomDummyNodeManager.DummyNodeClassName = 'dummyNode'; DomDummyNodeManager.DummyNodeBeforeFirstTagClassName = 'beforefirstTag'; DomDummyNodeManager.DummyNodeContent = String.fromCharCode(160); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class XmlCursorPos { /** * @param {?} position * @return {?} */ static getXmlCursorPositionDebugName(position) { switch (position) { case XMLCursorPositions.CursorOnCompleteNode: return 'CursorOnCompleteNode'; case XMLCursorPositions.CursorBehindNode: return 'CursorBehindNode'; case XMLCursorPositions.CursorInEmptyNode: return 'CursorInEmptyNode'; case XMLCursorPositions.CursorInsideTextNode: return 'CursorInsideTextNode'; case XMLCursorPositions.CursorInFrontOfNode: return 'CursorInFrontOfNode'; default: return `unknown cursorPos ${position}`; } } /** * @param {?} pos * @return {?} */ static getSelectionForPos(pos) { if (DomDummyNodeManager.IsDummyNode(pos.ActualNode)) { throw new Error(`XmlCursorPos.getSelectionForPos: pos.AktNode ${pos.getDebugDescription()} is a dummy node!`); } if (DomDummyNodeManager.IsDummyNode(pos.ActualNode.parentElement)) { throw new Error(`XmlCursorPos.getSelectionForPos: pos.AktNode.parentElement ${pos.getDebugDescription()} is a dummy node!`); } switch (pos.PosAtNode) { case XMLCursorPositions.CursorOnCompleteNode: // (0) /** @type {?} */ let firstChild = pos.ActualNode.firstChild; if (DomDummyNodeManager.IsBeforeFirstTagDummyNode(firstChild)) { return { node: firstChild.firstChild, // text inside BeforeFirstTagDummyNod offset: 0 }; } else { return { node: pos.ActualNode, offset: 0 }; } case XMLCursorPositions.CursorInFrontOfNode: // (1) // (1) if (pos.ActualNode.nodeType === Node.TEXT_NODE) { /** @type {?} */ const dummyBefore = pos.ActualNode.previousSibling; if (DomDummyNodeManager.IsDummyNode(dummyBefore)) { return { node: dummyBefore.firstChild, // before text node offset: 1 }; } else { throw new Error('XMLCursorPositions.CursorInFrontOfNode (1):text node: is no dummy node: ' + pos.getDebugDescription()); } } else { // no text node firstChild = pos.ActualNode.firstChild; if (DomDummyNodeManager.IsBeforeFirstTagDummyNode(firstChild)) { return { node: firstChild.firstChild, // text inside BeforeFirstTagDummyNod offset: 1 }; } if (Xml2html.isNoClosingElement(pos.ActualNode) && DomDummyNodeManager.IsDummyNode(firstChild)) { return { node: firstChild.firstChild, offset: 1 }; } throw new Error('XMLCursorPositions.CursorInFrontOfNode (1):no text node: dont know what to do! '); } break; case XMLCursorPositions.CursorBehindNode: // (7, 11) /** @type {?} */ const dummyAfter = pos.ActualNode.nextSibling; if (DomDummyNodeManager.IsDummyNode(dummyAfter)) { return { node: dummyAfter.firstChild, // before text node offset: 0 }; } else { throw new Error(`XMLCursorPositionen.CursorBehindNode should only be set, when the element has no next sibling.`); } case XMLCursorPositions.CursorInEmptyNode: /** @type {?} */ const lastChild = pos.ActualNode.lastChild; if (!DomDummyNodeManager.IsDummyNode(firstChild) && !DomDummyNodeManager.IsBeforeFirstTagDummyNode(firstChild)) { return { node: lastChild.firstChild, // text inside DummyNode offset: 0 }; } break; case XMLCursorPositions.CursorInsideTextNode: return { node: pos.ActualNode, offset: pos.PosInTextnode }; } throw new Error(`showCursor.getSelectionForPos unknown CursorPos ${pos.getDebugDescription()}`); } constructor() { this.ActualNode = null; // no node selected this.PosAtNode = XMLCursorPositions.CursorOnCompleteNode; this.PosInTextnode = 0; } /** * @return {?} */ clone() { /** @type {?} */ const klon = new XmlCursorPos(); klon.SetCursor(this.ActualNode, this.PosAtNode, this.PosInTextnode); return klon; } /** * @param {?} otherPos * @return {?} */ equals(otherPos) { if (!otherPos) { return false; } if (otherPos.ActualNode !== this.ActualNode) { return false; } if (otherPos.PosAtNode !== this.PosAtNode) { return false; } if (this.PosAtNode === XMLCursorPositions.CursorInsideTextNode && this.PosInTextnode !== otherPos.PosInTextnode) { return false; } return true; } /** * @return {?} */ getDebugDescription() { return `node:${XmlToolbox.GetNodeDebugContext(this.ActualNode)}, pos:${XmlCursorPos.getXmlCursorPositionDebugName(this.PosAtNode)}, posInText:${this.PosInTextnode}`; } /** * @param {?} actualNode * @param {?} posAtNode * @param {?=} posInTextnode * @return {?} */ SetCursor(actualNode, posAtNode, posInTextnode = 0) { if (!actualNode) { throw new Error('actualNode is null'); } if (actualNode !== this.ActualNode) ; else { if (posAtNode !== this.PosAtNode) ; else { if (posInTextnode !== this.PosInTextnode) ; } } this.ActualNode = actualNode; this.PosAtNode = posAtNode; this.PosInTextnode = posInTextnode; } /** * @param {?} node * @return {?} */ setCursorBehindNodeForDefaultOrRightMovement(node) { /** @type {?} */ const nextSibling = DomDummyNodeManager.getNextSibling(node); if (nextSibling) { this.SetCursor(nextSibling, XMLCursorPositions.CursorInFrontOfNode); } else { this.SetCursor(node, XMLCursorPositions.CursorBehindNode); } } /** * @param {?} node * @return {?} */ setCursorpBehindNodeForLeftMovement(node) { if (XmlToolbox.IsTextOrCommentNode(node)) { // For a text node, the cursor is placed after the last character. this.SetCursor(node, XMLCursorPositions.CursorInsideTextNode, Math.max(0, node.textContent.length - 1)); } else { /** @type {?} */ const childNodeCount = DomDummyNodeManager.getChildNodeCount(node); if (childNodeCount === 0) { // There are no children in the node. if (!Xml2html.isNoClosingElement(node)) { // If the cursor shows a close tag, then put it in the empty node this.SetCursor(node, XMLCursorPositions.CursorInEmptyNode); } else { // If the cursor does not display a close tag, then place it before the empty node. this.SetCursor(node, XMLCursorPositions.CursorInFrontOfNode); } } else { // There are children in node this.SetCursor(DomDummyNodeManager.getLastChild(node), XMLCursorPositions.CursorBehindNode); } } } } /** @enum {number} */ const XMLCursorPositions = { CursorInFrontOfNode: 0, CursorOnCompleteNode: 1, CursorInEmptyNode: 2, CursorInsideTextNode: 3, CursorBehindNode: 4, }; XMLCursorPositions[XMLCursorPositions.CursorInFrontOfNode] = 'CursorInFrontOfNode'; XMLCursorPositions[XMLCursorPositions.CursorOnCompleteNode] = 'CursorOnCompleteNode'; XMLCursorPositions[XMLCursorPositions.CursorInEmptyNode] = 'CursorInEmptyNode'; XMLCursorPositions[XMLCursorPositions.CursorInsideTextNode] = 'CursorInsideTextNode'; XMLCursorPositions[XMLCursorPositions.CursorBehindNode] = 'CursorBehindNode'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class XmlCursorPosMover { /** * @param {?} pos * @param {?} rootNode * @param {?} xmlRules * @return {?} */ static moveLeft(pos, rootNode, xmlRules) { if (pos === undefined) { return false; } switch (pos.PosAtNode) { case XMLCursorPositions.CursorOnCompleteNode: // Put in front of the node pos.SetCursor(pos.ActualNode, XMLCursorPositions.CursorInFrontOfNode); break; case XMLCursorPositions.CursorInFrontOfNode: if (pos.ActualNode === rootNode) { return false; } else { /** @type {?} */ const previousSibling = DomDummyNodeManager.getPreviousSibling(pos.ActualNode); if (previousSibling) { // Previous sibling node exists pos.setCursorpBehindNodeForLeftMovement(previousSibling); } else { // no previous sibling node available -> set before parent node pos.SetCursor(pos.ActualNode.parentNode, XMLCursorPositions.CursorInFrontOfNode); } } break; case XMLCursorPositions.CursorBehindNode: pos.setCursorpBehindNodeForLeftMovement(pos.ActualNode); break; case XMLCursorPositions.CursorInEmptyNode: // Put in front of the node pos.SetCursor(pos.ActualNode, XMLCursorPositions.CursorInFrontOfNode); break; case XMLCursorPositions.CursorInsideTextNode: if (XmlToolbox.IsTextOrCommentNode(pos.ActualNode)) { // Node is Textnode if (pos.PosInTextnode > 1) { // Cursor one character to the left pos.SetCursor(pos.ActualNode, pos.PosAtNode, pos.PosInTextnode - 1); } else { // Put in front of the node pos.SetCursor(pos.ActualNode, XMLCursorPositions.CursorInFrontOfNode); } } else { /** @type {?} */ const elemt = /** @type {?} */ (pos.ActualNode); throw new Error(`XMLCursorPos.MoveLeft: CursorPos is XMLCursorPositionen.CursorInnerhalbDesTextNodes, but no text node has been chosen, but the node ${elemt ? elemt.outerHTML : ''}`); } break; default: throw new Error(`XMLCursorPos.MoveLeft: unknown CursorPos ${pos.PosAtNode}`); } return true; } /** * @param {?} pos * @param {?} rootNode * @param {?} xmlRules * @return {?} */ static moveRight(pos, rootNode, xmlRules) { if (pos === undefined) { return false; } switch (pos.PosAtNode) { case XMLCursorPositions.CursorOnCompleteNode: /** @type {?} */ const tagName = Xml2html.getTagNameFromNode(pos.ActualNode); /** @type {?} */ const rule = xmlRules.ruleElements.get(tagName); if (rule && rule.empty === true) { pos.setCursorBehindNodeForDefaultOrRightMovement(pos.ActualNode); } else { /** @type {?} */ const firstChild = DomDummyNodeManager.getFirstChild(pos.ActualNode); if (firstChild) { pos.SetCursor(firstChild, XMLCursorPositions.CursorInFrontOfNode); } else { pos.SetCursor(pos.ActualNode, XMLCursorPositions.CursorInEmptyNode); } } break; case XMLCursorPositions.CursorBehindNode: /** @type {?} */ const nextSibling = DomDummyNodeManager.getNextSibling(pos.ActualNode); if (nextSibling) { /** @type {?} */ const elemt = /** @type {?} */ (pos.ActualNode); throw new Error(`XMLCursorPos.MoveRight: cursor behind node should only be set, when no other siblings are following ${elemt ? elemt.outerHTML : ''}`); } else { /** @type {?} */ const parentNextSibling = DomDummyNodeManager.getNextSibling(pos.ActualNode.parentElement); if (parentNextSibling) { pos.SetCursor(parentNextSibling, XMLCursorPositions.CursorInFrontOfNode); } else { pos.SetCursor(pos.ActualNode.parentElement, XMLCursorPositions.CursorBehindNode); } } break; case XMLCursorPositions.CursorInEmptyNode: // Place behind the node pos.setCursorBehindNodeForDefaultOrRightMovement(pos.ActualNode); break; case XMLCursorPositions.CursorInFrontOfNode: if (XmlToolbox.IsTextOrCommentNode(pos.ActualNode)) { // Node is Textnode if ((/** @type {?} */ (pos.ActualNode)).textContent.length > 1) { // Textnode is not empty pos.SetCursor(pos.ActualNode, XMLCursorPositions.CursorInsideTextNode, 1); // one character forward, behind the first character } else { // Textnode is empty // put behind the node pos.SetCursor(pos.ActualNode, XMLCursorPositions.CursorBehindNode); // Because "CursorBehindNode" is not attractive, one more step this.moveRight(pos, rootNode, xmlRules); } } else { /** @type {?} */ const childNodeCount = DomDummyNodeManager.getChildNodeCount(pos.ActualNode); if (childNodeCount === 0) { // no Children existing if (Xml2html.isNoClosingElement(pos.ActualNode)) { // If no closed tag is displayed for this node, then directly behind the node // Place behind the node pos.setCursorBehindNodeForDefaultOrRightMovement(pos.ActualNode); } else { // Node has closing tag, so put in between // Place in empty node pos.SetCursor(pos.ActualNode, XMLCursorPositions.CursorInEmptyNode); } } else { /** @type {?} */ const firstChild = DomDummyNodeManager.getFirstChild(pos.ActualNode); pos.SetCursor(firstChild, XMLCursorPositions.CursorInFrontOfNode); } } break; case XMLCursorPositions.CursorInsideTextNode: if (XmlToolbox.IsTextOrCommentNode(pos.ActualNode)) { // Node is Textnode if ((/** @type {?} */ (pos.ActualNode)).textContent.length > pos.PosInTextnode + 1) { // there is still text in the right of the textnode // step one character forwards: behind the first char pos.SetCursor(pos.ActualNode, pos.PosAtNode, pos.PosInTextnode + 1); } else { // no text after the textnode // place cursor behind the node pos.setCursorBehindNodeForDefaultOrRightMovement(pos.ActualNode); } } else { /** @type {?} */ const elemt = /** @type {?} */ (pos.ActualNode); throw new Error(`XMLCursorPos.MoveRight: CursorPos is XMLCursorPositionen.CursorInnerhalbDesTextNodes, but no text node has been chosen, but the node ${elemt ? elemt.outerHTML : ''}`); } break; default: throw new Error(`String.Format("XMLCursorPos.MoveRight: unknown CursorPos ${pos.PosAtNode}`); } return true; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class XmlCursor { constructor() { this.xmlDocumentRootNode = undefined; this.domDummyNodeManager = new DomDummyNodeManager(); this.StartPos = new XmlCursorPos(); this.EndPos = new XmlCursorPos(); } /** * is there nothing between start and end pos? * @return {?} */ isEmptySelection() { if (this.StartPos.equals(this.EndPos)) { return true; } return false; } /** * @return {?} */ sortStartAndEnd() { if (this.StartPos.ActualNode && this.EndPos.ActualNode) { if (this.StartPos.ActualNode.isSameNode(this.EndPos.ActualNode)) { if (this.StartPos.PosAtNode === XMLCursorPositions.CursorInsideTextNode && this.EndPos.PosAtNode === XMLCursorPositions.CursorInsideTextNode && this.StartPos.PosInTextnode > this.EndPos.PosInTextnode) { this.swapStartAndEnd(); } } else { /** @type {?} */ const compared = this.StartPos.ActualNode.compareDocumentPosition(this.EndPos.ActualNode); // tslint:disable-next-line:no-bitwise if (!(compared & Node.DOCUMENT_POSITION_FOLLOWING)) { this.swapStartAndEnd(); } } } } /** * @return {?} */ swapStartAndEnd() { /** @type {?} */ const node = this.StartPos.ActualNode; /** @type {?} */ const pos = this.StartPos.PosAtNode; /** @type {?} */ const text = this.StartPos.PosInTextnode; this.StartPos.SetCursor(this.EndPos.ActualNode, this.EndPos.PosAtNode, this.EndPos.PosInTextnode); this.EndPos.SetCursor(node, pos, text); } /** * @return {?} */ show() { if (window.getSelection) { /** @type {?} */ const selection = window.getSelection(); this.show2(selection); } else { throw new Error('showCursor: unable to get "window.getSelection"'); } } /** * @param {?} selection * @return {?} */ show2(selection) { if (!selection) { return; } /** @type {?} */ const elems = document.querySelectorAll('.selected'); [].forEach.call(elems, function (el) { el.classList.remove('selected'); }); /** @type {?} */ const startSelection = XmlCursorPos.getSelectionForPos(this.StartPos); if (this.EndPos && this.EndPos.ActualNode && !this.EndPos.equals(this.StartPos)) { /** @type {?} */ const endSelection = XmlCursorPos.getSelectionForPos(this.EndPos); selection.setBaseAndExtent(startSelection.node, startSelection.offset, endSelection.node, endSelection.offset); } else { // only start pos selection.setPosition(startSelection.node, startSelection.offset); // select tag by adding "select" class if (this.StartPos.PosAtNode === XMLCursorPositions.CursorOnCompleteNode) { /** @type {?} */ const asElem = /** @type {?} */ (this.StartPos.ActualNode); if (asElem && asElem.classList) { asElem.classList.add('selected'); console.log('selected!'); } else { console.error(`no asElem.classList for select! ${XmlToolbox.GetNodeDebugContext(asElem)}`); } } } } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class XmlCursorPosSetter { /** * @param {?} pos * @param {?} selectionAnchorNode * @param {?} selectionAnchorOffset * @param {?=} debugLog * @return {?} */ static setFromSelection(pos, selectionAnchorNode, selectionAnchorOffset, debugLog = null) { if (selectionAnchorNode !== undefined && selectionAnchorNode !== null) { /** @type {?} */ let node = /** @type {?} */ (selectionAnchorNode); if (DomDummyNodeManager.IsDummyNode(selectionAnchorNode.parentElement)) { // clicked inside a dummy node node = /** @type {?} */ (selectionAnchorNode.parentElement); if (DomDummyNodeManager.IsBeforeFirstTagDummyNode(node)) { switch (selectionAnchorOffset) { case 0: if (debugLog) { debugLog.setInputSelection(`(1) cursor here: #<tag> pos 0`); } pos.SetCursor(node.parentElement, XMLCursorPositions.CursorOnCompleteNode, 0); return; case 1: if (debugLog) { debugLog.setInputSelection(`(2) cursor here: #<tag> pos 1`); } pos.SetCursor(node.parentElement, XMLCursorPositions.CursorInFrontOfNode, 0); return; default: throw new Error(`selectionAnchorOffset=${selectionAnchorOffset}?!?`); } } if (node.parentElement.childNodes.length === 2) { if (debugLog) { debugLog.setInputSelection(`(13, 14) cursor here: <tag>#</tag>`); } pos.SetCursor(node.parentElement, XMLCursorPositions.CursorInEmptyNode, 0); return; } if (node === node.parentElement.lastChild && selectionAnchorOffset === 1) { if (debugLog) { debugLog.setInputSelection(`(8, 12) cursor here: #</tag>, selecting complete node`); } pos.SetCursor(node.parentElement, XMLCursorPositions.CursorOnCompleteNode, 0); return; } /** @type {?} */ const nextSibling = node.nextSibling; if (nextSibling) { if (debugLog) { debugLog.setInputSelection(`(3, 4, 9, 10) cursor here: #Text or here #<tag>`); } pos.SetCursor(nextSibling, XMLCursorPositions.CursorInFrontOfNode, 0); return; } /** @type {?} */ const previousSibling = node.previousSibling; if (previousSibling) { if (debugLog) { debugLog.setInputSelection(`(7, 11) cursor here: Text# or here <tag>#`); } pos.SetCursor(previousSibling, XMLCursorPositions.CursorBehindNode, 0); return; } } else { // is no dummy node if (selectionAnchorNode.nodeType === Node.TEXT_NODE) { if (selectionAnchorOffset === 0) { if (debugLog) { debugLog.setInputSelection(`(6) cursor here: #Text`); } pos.SetCursor(selectionAnchorNode, XMLCursorPositions.CursorInsideTextNode, 0); return; } if (selectionAnchorOffset === selectionAnchorNode.textContent.length) { if (debugLog) { debugLog.setInputSelection(`(7 Alternative) cursor here: Text#`); } pos.SetCursor(selectionAnchorNode, XMLCursorPositions.CursorInsideTextNode, selectionAnchorNode.textContent.length); return; } if (debugLog) { debugLog.setInputSelection(`(5) cursor here: Text#Text`); } if (selectionAnchorOffset > selectionAnchorNode.textContent.length) {