Types of links
There are various types of link that can be displayed in a HTML page. The most common ones are HTTP, WWW, HTTPS, FTP, NEWS, MMS and an email. The code detailed in this article handles those. You can easily adapt it to add more types of links you would like to handle.
Considerations
While there are some tools that will do that for you, I have never found one that can cover what I want in terms of specific considerations about what is the content that should be linked and what is not. For example, many tools such as the one included in Visual FoxPro will hyperlink the content of a memo field when you are browsing a table but you can end up with unexpected results. If we take the Universal Thread message #1083829 content and insert it in a memo field, Visual FoxPro will properly hyperlink the related text. Here is the message on the Universal Thread:
If we take this message content and put it in a memo field, the related text will be properly hyperlinked:
For another example, we will take message #1071615. This one contains a link that is followed by a dot. The link has been inserted at the end of phrase. The message looks like this:
If we take this message content and put it in a memo field, the related text will not be properly hyperlinked:
As long as the link is inserted between spaces, quotes or other well known delimiters, many tools will hyperlink the link properly. But, on the Universal Thread, I have seen many variations where it is better to use a customized approach. This allows you to further customize it when you will encounter a situation that need to be handled differently.
Calling the function
In order to properly use the function, I have isolated the various requests for hyperlinks in AutoLinkAllLink() and the main processes in AutoLinkHttp() and AutoLinkEmail(). Basically, the only parameter I need to pass is the memo field content. Here is an example of a simple call:
LOCAL lmNotes lmNotes=Thread.Notes lcHtml=AutoLinkAllLink(lmNotes)
* Auto link only links * expM1 Memo field FUNCTION AutoLinkAllLink PARAMETER tmNotes LOCAL lmNotes lmNotes=tmNotes lmNotes=AutoLinkHttp(lmNotes,'HTTP://') lmNotes=AutoLinkHttp(lmNotes,'WWW.',1) lmNotes=AutoLinkHttp(lmNotes,'HTTPS://') lmNotes=AutoLinkHttp(lmNotes,'FTP://') lmNotes=AutoLinkHttp(lmNotes,'NEWS://') lmNotes=AutoLinkHttp(lmNotes,'MMS://') lmNotes=AutoLinkEmail(lmNotes) RETURN lmNotes
LOCAL lmNotes lmNotes=Thread.Notes lcHtml=AutoLinkHttp(lmNotes,'HTTP://')
AutoLinkHttp()
The AutoLinkHttp() function receives three parameters:
This function handles special syntaxes of references such as when a link is between parentheses, quotes, smaller and greater signs, and so on. In order to properly hyperlink a link, a valid link should be found. If a link is found but is not well terminated, the function will simply ignore this link and proceed with the next one. Note also that the function adds a Target="_blank" to navigate to the link in a new browser window. This can be easily adjusted to fit your own needs.
Here is the code of that function:
* Auto link the http references * expM1 Memo * expC1 String to search for * expN1 Pass 1 to add the HTTP FUNCTION AutoLinkHttp PARAMETER tmNotes,tcSearchFor,tnAddHttp LOCAL lmNotes,lnCompteur,lnLocation,lcLink,lcString,lnLocation2,lnCompteur2 LOCAL lcFirst,lcUntilLink,lnCompteur3,lnLenUntilLink,llTag,lnOccur lmNotes=tmNotes lnOccurence=OCCUR(tcSearchFor,UPPER(lmNotes)) lnCompteur2=0 FOR lnCompteur=1 TO lnOccurence lnCompteur2=lnCompteur2+1 lnLocation=ATC(tcSearchFor,lmNotes,lnCompteur2) lcFirst=SUBSTR(lmNotes,lnLocation-1,1) IF INLIST(lcFirst,' ',CHR(13),CHR(10),'(',':','>','"',CHR(9)) OR lnLocation=1 lcString=SUBSTR(lmNotes,lnLocation) lnLocation2=AT(' ',lcString) lnLocation3=AT(CHR(13),lcString) IF lnLocation3>0 IF lnLocation2=0 OR lnLocation2>lnLocation3 lnLocation2=lnLocation3 ENDIF ENDIF lnLocation3=AT(CHR(10),lcString) IF lnLocation3>0 IF lnLocation2=0 OR lnLocation2>lnLocation3 lnLocation2=lnLocation3 ENDIF ENDIF lnLocation3=AT('>',lcString) IF lnLocation3>0 IF lnLocation2=0 OR lnLocation2>lnLocation3 lnLocation2=lnLocation3 ENDIF ENDIF lnLocation3=AT('<',lcString) IF lnLocation3>0 IF lnLocation2=0 OR lnLocation2>lnLocation3 lnLocation2=lnLocation3 ENDIF ENDIF lnLocation3=AT('"',lcString) IF lnLocation3>0 IF lnLocation2=0 OR lnLocation2>lnLocation3 lnLocation2=lnLocation3 ENDIF ENDIF * The inclusion of parentheses in URL are now widely used. Many sites also * makes us of those and sometimes, we have parentheses inside parentheses. * When this is the case, we don't process them. Basically, if someone posts a link * without adding any space at the end, such as adding a parenthese right after * the link, then we ignore this link. lnLocation3=AT(')',lcString) IF lnLocation3>0 IF lnLocation2=0 OR lnLocation2>lnLocation3 lnLocation2=lnLocation3 ENDIF * If we have a starting parenthese within the string, it's ok * Otherwise, it means that the end parenthese represents the beginning * of a new string IF AT('(',lcString)=0 IF lnLocation2=0 OR lnLocation2>lnLocation3 lnLocation2=lnLocation3 ENDIF ENDIF ENDIF * If the space was not found, then we have to initialize lnLocation2 with * the length of the string. Otherwise, we will end up by including the dot * at the end when it occurs IF lnLocation2=0 lnLocation2=LEN(lcString)+1 ENDIF lcLink=SUBSTR(lcString,1,lnLocation2-1) * If last character is a dot IF SUBSTR(lcLink,LEN(lcLink))='.' lcLink=SUBSTR(lcLink,1,LEN(lcLink)-1) ENDIF * If last character is a comma IF SUBSTR(lcLink,LEN(lcLink))=',' lcLink=SUBSTR(lcLink,1,LEN(lcLink)-1) ENDIF * We need to verify that the text is not within a anchor IF TextWithinAnchor(lmNotes,lnLocation) LOOP ENDIF * We need to verify that the text is not within a PRE tag IF TextWithinPRE(lmNotes,lnLocation) LOOP ENDIF lmNotes=SUBSTR(lmNotes,1,lnLocation-1)+; '<A HREF='+IIF(TYPE('tnAddHttp')='N','http://','')+lcLink+; ' TARGET="_blank">'+lcLink+'</A>'+; SUBSTR(lmNotes,lnLocation+LEN(lcLink)) lnCompteur2=lnCompteur2+1 * On some particular situations, where an HTTP link contains another HTTP link, * such as this one: * http://www2.office.daquas.cz/index.htm?http://www2.office.daquas.cz/fo * We have to add the number of extra http references to the counter * By that, we will make sure to continue with the next HTTP link lnOccur=OCCUR(tcSearchFor,UPPER(lcLink)) IF lnOccur>1 lnCompteur2=lnCompteur2+lnOccur-1 ENDIF ENDIF NEXT RETURN lmNotes
AutoLinkEmail()
The AutoLinkEmail() is also another great function which hyperlinks email links. As email has its own syntax, we need to handle this process in a separated function. Note that this function also verifies for special syntaxes of references but also verifies for minimum requirement in regards to email validations such as verifying for the presence of the @ character, verify for the presence of a dot after the @ character and so on. I have a more detailed function that applies much more validations on a link to make sure this has the minimum requirements to be defined as an email link but for the hyperlink purposes, this helps me a lot.
* Auto link the email references * expM1 Memo FUNCTION AutoLinkEmail PARAMETER tmNotes LOCAL lmNotes,lnCompteur,lnLocation,lcLink,lcString,lnLocation2,lnCompteur2 LOCAL lcFirst,lcUntilLink,lnCompteur3,lnLenUntilLink,llTag LOCAL lcAfterAt,lcFirstPart,lcFirstPartCorrected,lcLastPart lmNotes=tmNotes lnOccurence=OCCUR('@',lmNotes) lnCompteur2=0 FOR lnCompteur=1 TO lnOccurence lnCompteur2=lnCompteur2+1 lnLocation=ATC('@',lmNotes,lnCompteur2) * We need to find the location of the email lnLocation2=RAT(' ',LEFT(lmNotes,lnLocation)) lnLocation2=MAX(lnLocation2,RAT(CHR(13),LEFT(lmNotes,lnLocation))) lnLocation2=MAX(lnLocation2,RAT(CHR(10),LEFT(lmNotes,lnLocation))) lnLocation2=MAX(lnLocation2,RAT('(',LEFT(lmNotes,lnLocation))) lnLocation2=MAX(lnLocation2,RAT(':',LEFT(lmNotes,lnLocation))) lnLocation2=MAX(lnLocation2,RAT('>',LEFT(lmNotes,lnLocation))) lnLocation2=MAX(lnLocation2,RAT('"',LEFT(lmNotes,lnLocation))) * If the location of the email is the character before the @ IF lnLocation2=lnLocation-1 LOOP ENDIF * We need to verify that the text is not within a anchor IF TextWithinAnchor(lmNotes,lnLocation) LOOP ENDIF * We need to verify that the text is not within a PRE tag IF TextWithinPRE(lmNotes,lnLocation) LOOP ENDIF lcString=SUBSTR(lmNotes,lnLocation) * We have to find the position of the last character of the email until we find * one that is not in the list of delimiter lnLocation3=GetEmailLastCharacter(lcString) * If lnLocation3+ is smaller than 3 character, it's not good IF lnLocation3<3 LOOP ENDIF * Get the email lcEmail=SUBSTR(lmNotes,lnLocation2+1,lnLocation3+(lnLocation-lnLocation2-1)) * String after the @ lnLocation3=AT('@',lcEmail) lcAfterAt=SUBSTR(lcEmail,lnLocation3+1) * If there is no dot in the string IF AT('.',lcAfterAt)=0 LOOP ENDIF * Each character of the first part should be A-Z, a-z, 0-9, '-' (dash), '.'(dot) and * '_' (underscore) lcFirstPart=UPPER(SUBSTR(lcEmail,1,lnLocation3-1)) lcFirstPartCorrected=CHRTRAN(lcFirstPart,; CHRTRAN(lcFirstPart,'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._',''),'') IF LEN(lcFirstPart)<>LEN(lcFirstPartCorrected) LOOP ENDIF * Each character of the last part should be A-Z, a-z, 0-9, '-' (dash), '.'(dot) and * and '_' (underscore) lcAfterAt=UPPER(lcAfterAt) lcFirstPartCorrected=CHRTRAN(lcAfterAt,; CHRTRAN(lcAfterAt,'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._',''),'') IF LEN(lcAfterAt)<>LEN(lcFirstPartCorrected) LOOP ENDIF * The last section of the last part can only include between two and four characters lcLastPart=SUBSTR(lcAfterAt,RAT('.',lcAfterAt)+1) IF LEN(lcLastPart)<2 OR LEN(lcLastPart)>4 LOOP ENDIF lmNotes=SUBSTR(lmNotes,1,lnLocation2)+; '<A HREF=mailto:'+lcEmail+'>'+lcEmail+'</A>'+; SUBSTR(lmNotes,lnLocation2+LEN(lcEmail)+1) lnCompteur2=lnCompteur2+1 NEXT RETURN lmNotes
Those functions handle most of the various possibilities of finding links that should be hyperlinked. However, it is quite possible that there are most situations that are not being taken in considerations. Some users sometimes will add special characters before or after the link where I would have to readjust and decide if this should be a link or not. Sometimes, even by looking at the memo field content, it is not easy to know if a specific portion of text should be hyperlinked or not. One example if when a link is added in a message without being followed by a proper separator.
I hope this article gave you additional ideas and considerations on how to manage those links.
Source code