UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

168 lines (144 loc) 5.47 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ /** * <code>FormattedText</code> helper. * This class handles the <code>AnchorGeneration</code> for the <code>FormattedText</code> control. */ sap.ui.define([ "sap/ui/base/Object", "sap/m/library", "sap/base/security/URLListValidator" ], function(BaseObject, library, URLListValidator) { "use strict"; // shortcut for sap.m.LinkConversion var LinkConversion = library.LinkConversion; var AnchorGenerator = BaseObject.extend("sap.m.FormattedTextAnchorGenerator", { getInterface: function() { return this; // no facade } }); var LINK_SEARCH_PATTERN = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;()$]*[-A-Z0-9+&@#\/%=~_|])/gim; var WWW_DETECTION_PATTERN = /(www\.[^\s><]+(\b|$))/gim; var WWW_DETECTED_LINKS_PREFIX = "//"; var DETECT_HTML_TAGS = /<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+(?!\/\s\*)>/gim; var EXISTING_ANCHOR_TAGS = /<a[^>]*>([^<]+)<\/a>/gim; var ENTITIES_TO_SKIP = [DETECT_HTML_TAGS, EXISTING_ANCHOR_TAGS]; /** * Generates anchors based on the provided configuration. * @param {string} sText The text to be processed * @param {sap.m.LinkConversion} sLinkConversionStrategy The link conversion strategy * @param {string} sTarget The target attribute of the newly created anchors * @static * @public * @returns {string} The resulting text after the anchor generation */ AnchorGenerator.generateAnchors = function (sText, sLinkConversionStrategy, sTarget) { if (sLinkConversionStrategy === LinkConversion.ProtocolOnly) { sText = AnchorGenerator._createAnchors(sText, LINK_SEARCH_PATTERN, sTarget); } if (sLinkConversionStrategy === LinkConversion.All) { sText = AnchorGenerator._createAnchors(sText, LINK_SEARCH_PATTERN, sTarget); sText = AnchorGenerator._createAnchors(sText, WWW_DETECTION_PATTERN, sTarget, WWW_DETECTED_LINKS_PREFIX); } return sText; }; /** * Creates a positioning object from a starting point and length. * @param {number} iIndex * @param {number} iLength * @returns {{iStartPos: (number), iEndPos: (number)}} * @private */ AnchorGenerator._createPositionObject = function (iIndex, iLength) { return {iStartPos: iIndex, iEndPos: iIndex + iLength}; }; /** * Checks if the second object is nested inside the first one. * @param {object} oFirst * @param {object} oSecond * @returns {boolean} * @private */ AnchorGenerator._isNested = function (oFirst, oSecond) { return oFirst.iStartPos < oSecond.iStartPos && oFirst.iEndPos > oSecond.iEndPos; }; /** * Checks if any of the excluded positions coincide with the newly discovered link that's about to be created. * @param {Array<Object>} aExcludedPositions * @param {object} oCandidateAnchor * @returns {*} * @private */ AnchorGenerator._isAllowed = function (aExcludedPositions, oCandidateAnchor) { return aExcludedPositions.some(function (oExcludedPosition) { return AnchorGenerator._isNested(oExcludedPosition, oCandidateAnchor); }); }; /** * Checks if the necessary preconditions for creating an anchor are met. * @param {string} sUrlCandidate * @param {object} oCandidatePosition * @param {Array<Object>} aExcludedPositions * @returns {boolean} * @private */ AnchorGenerator._shouldBeProcessed = function (sUrlCandidate, oCandidatePosition, aExcludedPositions) { return URLListValidator.validate(sUrlCandidate) && !AnchorGenerator._isAllowed(aExcludedPositions, oCandidatePosition); }; /** * Scans for entities that shouldn't be processed (should be excluded). * @param {regexp} rSearchPattern * @param {string} sText * @returns {Array<Object>} * @private */ AnchorGenerator._scanForEntitiesToSkip = function (rSearchPattern, sText) { var aExistingAnchors = [], aCurrentMatch; while ((aCurrentMatch = rSearchPattern.exec(sText)) !== null) { aExistingAnchors.push(AnchorGenerator._createPositionObject(aCurrentMatch.index, aCurrentMatch[0].length)); } return aExistingAnchors; }; /** * Retrieves the excluded entities in the text. * @param {string} sText * @returns {Array.<{iStartPos: (number), iEndPos: (number)}>} * @private */ AnchorGenerator._getEntitiesToSkipWhileSearchingForLinks = function (sText) { return ENTITIES_TO_SKIP .map(function (sSearchPattern) { return AnchorGenerator._scanForEntitiesToSkip(sSearchPattern, sText); }) .reduce(function (a, b) { return a.concat(b); }); }; /** * Creates anchors from the discovered links and the supplied pattern. * @param {string} sText * @param {regex} rPattern * @param {string} sTarget * @param {string} sPrefix * @returns {string} * @private */ AnchorGenerator._createAnchors = function (sText, rPattern, sTarget, sPrefix) { var aExistingAnchors = AnchorGenerator._getEntitiesToSkipWhileSearchingForLinks(sText), fnCreateAnchor; sPrefix = sPrefix || ''; fnCreateAnchor = function (sPotentialLink) { var oCandidatePosition = AnchorGenerator._createPositionObject(arguments[3], sPotentialLink.length); if (!AnchorGenerator._shouldBeProcessed(sPotentialLink, oCandidatePosition, aExistingAnchors)) { return sPotentialLink; } return "<a href=\"" + sPrefix + sPotentialLink + "\" target=\"" + sTarget + "\">" + sPotentialLink + "</a>"; }; return sText.replace(rPattern, fnCreateAnchor); }; return AnchorGenerator; });