@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
168 lines (144 loc) • 5.47 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 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;
});