diff --git a/Makefile b/Makefile
index f150f668..1cee1b05 100644
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ OBJS = swad_account.o swad_account_database.o swad_action.o swad_admin.o \
swad_admin_database.o swad_agenda.o swad_agenda_database.o swad_alert.o \
swad_announcement.o swad_announcement_database.o swad_API.o \
swad_API_database.o swad_assignment.o swad_assignment_database.o \
- swad_attendance.o swad_attendance_database.o \
+ swad_attendance.o swad_attendance_database.o swad_autolink.o \
swad_banner.o swad_banner_database.o swad_box.o swad_browser.o \
swad_browser_database.o swad_building.o swad_building_database.o \
swad_button.o \
diff --git a/swad_API.c b/swad_API.c
index e3a9e5c8..26c8d4e6 100644
--- a/swad_API.c
+++ b/swad_API.c
@@ -105,6 +105,7 @@ cp -f /home/acanas/swad/swad/swad /var/www/cgi-bin/
#include "swad_API.h"
#include "swad_API_database.h"
#include "swad_attendance_database.h"
+#include "swad_autolink.h"
#include "swad_browser.h"
#include "swad_browser_database.h"
#include "swad_course_database.h"
@@ -1426,7 +1427,7 @@ static int API_WritePlainTextIntoHTMLBuffer (struct soap *soap,
/* Convert to respectful HTML and insert links */
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
TxtHTML,Cns_MAX_BYTES_LONG_TEXT,false); // Convert from HTML to recpectful HTML
- Str_InsertLinks (TxtHTML,Cns_MAX_BYTES_LONG_TEXT,60); // Insert links
+ ALn_InsertLinks (TxtHTML,Cns_MAX_BYTES_LONG_TEXT,60); // Insert links
/* Write text */
fprintf (FileHTMLTmp,"%s",TxtHTML);
diff --git a/swad_RSS.c b/swad_RSS.c
index edf1357b..2e3c06b9 100644
--- a/swad_RSS.c
+++ b/swad_RSS.c
@@ -29,6 +29,7 @@
#include // For NULL
#include
+#include "swad_autolink.h"
#include "swad_call_for_exam_database.h"
#include "swad_changelog.h"
#include "swad_database.h"
@@ -199,7 +200,7 @@ static void RSS_WriteNotices (FILE *FileRSS,struct Crs_Course *Crs)
/* Write full content of the notice */
Str_Copy (Content,row[3],sizeof (Content) - 1);
- Str_InsertLinks (Content,Cns_MAX_BYTES_TEXT,40);
+ ALn_InsertLinks (Content,Cns_MAX_BYTES_TEXT,40);
fprintf (FileRSS,"%s %s %s:
%s
]]>\n",
UsrDat.FrstName,UsrDat.Surname1,UsrDat.Surname2,Content);
diff --git a/swad_agenda.c b/swad_agenda.c
index 8376e077..cff32be0 100644
--- a/swad_agenda.c
+++ b/swad_agenda.c
@@ -34,6 +34,7 @@
#include "swad_agenda.h"
#include "swad_agenda_database.h"
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_date.h"
@@ -860,7 +861,7 @@ static void Agd_ShowOneEvent (struct Agd_Agenda *Agenda,
Agd_DB_GetEventTxt (&AgdEvent,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Txt,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to recpectful HTML
- Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
HTM_Txt (Txt);
HTM_DIV_End ();
HTM_TD_End ();
diff --git a/swad_announcement.c b/swad_announcement.c
index 4bcb3256..87333e19 100644
--- a/swad_announcement.c
+++ b/swad_announcement.c
@@ -27,6 +27,7 @@
#include "swad_announcement.h"
#include "swad_announcement_database.h"
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -123,7 +124,7 @@ void Ann_ShowAllAnnouncements (void)
/* Get the subject (row[3]), the content (row[4]), and insert links */
Str_Copy (Subject,row[3],sizeof (Subject) - 1);
Str_Copy (Content,row[4],sizeof (Content) - 1);
- Str_InsertLinks (Content,Cns_MAX_BYTES_TEXT,50);
+ ALn_InsertLinks (Content,Cns_MAX_BYTES_TEXT,50);
/* Show the announcement */
Ann_DrawAnAnnouncement (AnnCod,Status,Subject,Content,
@@ -203,7 +204,7 @@ void Ann_ShowMyAnnouncementsNotMarkedAsSeen (void)
/* Get the subject (row[1]), the content (row[2]), and insert links */
Str_Copy (Subject,row[1],sizeof (Subject) - 1);
Str_Copy (Content,row[2],sizeof (Content) - 1);
- Str_InsertLinks (Content,Cns_MAX_BYTES_TEXT,50);
+ ALn_InsertLinks (Content,Cns_MAX_BYTES_TEXT,50);
/* Show the announcement */
Ann_DrawAnAnnouncement (AnnCod,Ann_ACTIVE_ANNOUNCEMENT,Subject,Content,
diff --git a/swad_assignment.c b/swad_assignment.c
index eea62679..5b3af630 100644
--- a/swad_assignment.c
+++ b/swad_assignment.c
@@ -34,6 +34,7 @@
#include "swad_assignment.h"
#include "swad_assignment_database.h"
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -491,7 +492,7 @@ static void Asg_ShowOneAssignment (struct Asg_Assignments *Assignments,
Asg_DB_GetAssignmentTxtByCod (Asg.AsgCod,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Txt,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to recpectful HTML
- Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
if (PrintView)
HTM_TD_Begin ("colspan=\"2\" class=\"LT\"");
else
diff --git a/swad_attendance.c b/swad_attendance.c
index 246be6bc..a7961ed7 100644
--- a/swad_attendance.c
+++ b/swad_attendance.c
@@ -35,6 +35,7 @@
#include "swad_attendance.h"
#include "swad_attendance_database.h"
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -551,7 +552,7 @@ static void Att_ShowOneAttEvent (struct Att_Events *Events,
Att_DB_GetAttEventDescription (Event->AttCod,Description);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Description,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to recpectful HTML
- Str_InsertLinks (Description,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Description,Cns_MAX_BYTES_TEXT,60); // Insert links
if (ShowOnlyThisAttEventComplete)
HTM_TD_Begin ("colspan=\"2\" class=\"LT\"");
else
diff --git a/swad_autolink.c b/swad_autolink.c
new file mode 100644
index 00000000..e8c11b00
--- /dev/null
+++ b/swad_autolink.c
@@ -0,0 +1,611 @@
+// swad_autolink.c: inserting automatic links in text
+
+/*
+ SWAD (Shared Workspace At a Distance),
+ is a web platform developed at the University of Granada (Spain),
+ and used to support university teaching.
+
+ This file is part of SWAD core.
+ Copyright (C) 1999-2021 Antonio Cañas Vargas
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+/*****************************************************************************/
+/********************************* Headers ***********************************/
+/*****************************************************************************/
+
+#define _GNU_SOURCE // For asprintf
+#include // For tolower
+#include // For NULL
+#include // For asprintf
+#include // For malloc and free
+#include // For string functions
+
+#include "swad_autolink.h"
+#include "swad_config.h"
+#include "swad_error.h"
+#include "swad_form.h"
+#include "swad_global.h"
+#include "swad_nickname.h"
+#include "swad_nickname_database.h"
+#include "swad_photo.h"
+#include "swad_string.h"
+#include "swad_user.h"
+
+/*****************************************************************************/
+/******************************* Private types *******************************/
+/*****************************************************************************/
+
+typedef enum
+ {
+ ALn_LINK_UNKNOWN = 0,
+ ALn_LINK_URL = 1,
+ ALn_LINK_NICK = 2,
+ } ALn_LinkType_t;
+
+struct ALn_Substring
+ {
+ char *Str; // Pointer to the first char of substring
+ size_t Len; // Length of the substring
+ };
+
+struct ALn_Link
+ {
+ ALn_LinkType_t Type;
+ struct ALn_Substring URLorNick;
+ struct
+ {
+ struct ALn_Substring Anchor1;
+ struct ALn_Substring Anchor2;
+ struct ALn_Substring Anchor3;
+ } Nick;
+ size_t AddedLengthUntilHere; // Total length of extra HTML code added until this link (included)
+ struct ALn_Link *Prev;
+ struct ALn_Link *Next;
+ };
+
+/*****************************************************************************/
+/******************** Global variables from other modules ********************/
+/*****************************************************************************/
+
+extern struct Globals Gbl;
+
+/*****************************************************************************/
+/*************************** Private prototypes ******************************/
+/*****************************************************************************/
+
+static void ALn_CreateFirstLink (struct ALn_Link **Link,
+ struct ALn_Link **LastLink);
+static void ALn_CreateNextLink (struct ALn_Link **Link,
+ struct ALn_Link **LastLink);
+static void ALn_FreeLinks (struct ALn_Link *LastLink);
+static ALn_LinkType_t ALn_CheckURL (char **PtrSrc,char PrevCh,
+ struct ALn_Link **Link,
+ struct ALn_Link **LastLink,
+ size_t MaxCharsURLOnScreen);
+static ALn_LinkType_t ALn_CheckNickname (char **PtrSrc,char PrevCh,
+ struct ALn_Link **Link,
+ struct ALn_Link **LastLink);
+static void ALn_CopySubstring (const struct ALn_Substring *PtrSrc,char **PtrDst);
+
+/*****************************************************************************/
+/**************************** Private constants ******************************/
+/*****************************************************************************/
+
+// For URLs the length of anchor is fixed, so it can be calculated once
+#define ALn_URL_ANCHOR_1 ""
+#define ALn_URL_ANCHOR_3 ""
+#define ALn_URL_ANCHOR_1_LENGTH (sizeof (ALn_URL_ANCHOR_1) - 1)
+#define ALn_URL_ANCHOR_2_LENGTH (sizeof (ALn_URL_ANCHOR_2) - 1)
+#define ALn_URL_ANCHOR_3_LENGTH (sizeof (ALn_URL_ANCHOR_3) - 1)
+#define ALn_URL_ANCHOR_TOTAL_LENGTH (ALn_URL_ANCHOR_1_LENGTH + ALn_URL_ANCHOR_2_LENGTH + ALn_URL_ANCHOR_3_LENGTH)
+
+static const struct ALn_Substring URLAnchor1 =
+ {
+ .Str = ALn_URL_ANCHOR_1,
+ .Len = ALn_URL_ANCHOR_1_LENGTH,
+ };
+static const struct ALn_Substring URLAnchor2 =
+ {
+ .Str = ALn_URL_ANCHOR_2,
+ .Len = ALn_URL_ANCHOR_2_LENGTH,
+ };
+static const struct ALn_Substring URLAnchor3 =
+ {
+ .Str = ALn_URL_ANCHOR_3,
+ .Len = ALn_URL_ANCHOR_3_LENGTH,
+ };
+
+/*****************************************************************************/
+/************** Insert automatic links in every URL or nickname **************/
+/*****************************************************************************/
+/*
+Insertion example:
+The web site of @rms is https://stallman.org/
+The web site of @rms is https://stallman.org/
+*/
+
+/*
+
+
+
+*/
+
+void ALn_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScreen)
+ {
+ size_t TxtLength;
+ char PrevCh = '\0';
+ char *PtrSrc;
+ char *PtrDst;
+ struct ALn_Link *Link;
+ struct ALn_Link *LastLink;
+ size_t Length;
+ size_t i;
+ struct ALn_Substring Limited; // URL displayed on screen (may be shorter than actual length)
+ const struct ALn_Substring *Anchor1;
+ const struct ALn_Substring *Anchor2;
+ const struct ALn_Substring *Anchor3;
+
+ /**************************************************************/
+ /***** Find starts and ends of links (URLs and nicknames) *****/
+ /**************************************************************/
+ ALn_CreateFirstLink (&Link,&LastLink);
+
+ for (PtrSrc = Txt;
+ *PtrSrc;)
+ /* Check if the next char is the start of a URL */
+ if ((Link->Type = ALn_CheckURL (&PtrSrc,PrevCh,
+ &Link,&LastLink,
+ MaxCharsURLOnScreen)) == ALn_LINK_UNKNOWN)
+ /* Check if the next char is the start of a nickname */
+ if ((Link->Type = ALn_CheckNickname (&PtrSrc,PrevCh,
+ &Link,&LastLink)) == ALn_LINK_UNKNOWN)
+ {
+ /* The next char is not the start of a URL or a nickname */
+ PrevCh = *PtrSrc;
+ PtrSrc++;
+ }
+
+ /**********************************************************************/
+ /***** If there are one or more links (URLs or nicknames) in text *****/
+ /**********************************************************************/
+ if (LastLink) // Not null ==> one or more links found
+ {
+ /***** Insert links from end to start of text,
+ only if there is enough space available in text *****/
+ TxtLength = strlen (Txt);
+ if (TxtLength + LastLink->AddedLengthUntilHere <= MaxLength)
+ for (Link = LastLink;
+ Link;
+ Link = Link->Prev)
+ {
+ /***** Set anchors *****/
+ switch (Link->Type)
+ {
+ case ALn_LINK_URL:
+ Anchor1 = &URLAnchor1;
+ Anchor2 = &URLAnchor2;
+ Anchor3 = &URLAnchor3;
+ break;
+ case ALn_LINK_NICK:
+ Anchor1 = &Link->Nick.Anchor1;
+ Anchor2 = &Link->Nick.Anchor2;
+ Anchor3 = &Link->Nick.Anchor3;
+ break;
+ default:
+ continue;
+ }
+
+ /***** Step 1: Move forward the text after the link (URL or nickname)
+ (it's mandatory to do the copy in reverse order
+ to avoid overwriting source) *****/
+ PtrSrc = (Link == LastLink) ? Txt + TxtLength :
+ Link->Next->URLorNick.Str - 1,
+ PtrDst = PtrSrc + Link->AddedLengthUntilHere,
+ Length = PtrSrc - (Link->URLorNick.Str + Link->URLorNick.Len - 1);
+ for (i = 0;
+ i < Length;
+ i++)
+ *PtrDst-- = *PtrSrc--;
+
+ /***** Step 2: Copy third part of anchor *****/
+ ALn_CopySubstring (Anchor3,&PtrDst);
+
+ /***** Step 3: Move forward the link (URL or nickname)
+ to be shown on screen *****/
+ switch (Link->Type)
+ {
+ case ALn_LINK_URL:
+ if (Link->URLorNick.Len <= MaxCharsURLOnScreen)
+ ALn_CopySubstring (&Link->URLorNick,&PtrDst);
+ else
+ {
+ /* Limit the length of URL */
+ if ((Limited.Str = malloc (Link->URLorNick.Len + 1)) == NULL)
+ Err_NotEnoughMemoryExit ();
+ strncpy (Limited.Str,Link->URLorNick.Str,Link->URLorNick.Len);
+ Limited.Str[Link->URLorNick.Len] = '\0';
+ Limited.Len = Str_LimitLengthHTMLStr (Limited.Str,MaxCharsURLOnScreen);
+ ALn_CopySubstring (&Limited,&PtrDst);
+ free (Limited.Str);
+ }
+ break;
+ case ALn_LINK_NICK:
+ ALn_CopySubstring (&Link->URLorNick,&PtrDst);
+ break;
+ default:
+ break;
+ }
+
+ /***** Step 4: Copy second part of anchor *****/
+ ALn_CopySubstring (Anchor2,&PtrDst);
+
+ /***** Step 5: Copy link into directive A
+ (it's mandatory to do the copy in reverse order
+ to avoid overwriting source URL or nickname) *****/
+ ALn_CopySubstring (&Link->URLorNick,&PtrDst);
+
+ /***** Step 6: Copy first part of anchor *****/
+ ALn_CopySubstring (Anchor1,&PtrDst);
+ }
+ }
+
+ /***********************************/
+ /***** Free memory for anchors *****/
+ /***********************************/
+ ALn_FreeLinks (LastLink);
+ }
+
+/***************************** Create first link ******************************/
+
+static void ALn_CreateFirstLink (struct ALn_Link **Link,
+ struct ALn_Link **LastLink)
+ {
+ /***** Reset last link pointer *****/
+ (*LastLink) = NULL;
+
+ /***** Allocate current link *****/
+ if (((*Link) = malloc (sizeof (struct ALn_Link))) == NULL)
+ Err_NotEnoughMemoryExit ();
+
+ /***** Initialize current link *****/
+ (*Link)->Prev = NULL;
+ (*Link)->Next = NULL;
+ }
+
+/***************************** Create next link ******************************/
+
+static void ALn_CreateNextLink (struct ALn_Link **Link,
+ struct ALn_Link **LastLink)
+ {
+ /***** Current link now is pointing to a correct link,
+ so set last link pointer to current link *****/
+ (*LastLink) = (*Link);
+
+ /***** Allocate next link *****/
+ if (((*Link)->Next = malloc (sizeof (struct ALn_Link))) == NULL)
+ Err_NotEnoughMemoryExit ();
+
+ /***** Initialize next link *****/
+ (*Link)->Next->Prev = *Link;
+ (*Link)->Next->Next = NULL;
+
+ /***** Change current link to just allocated link *****/
+ (*Link) = (*Link)->Next;
+ }
+
+/***************************** Free found links ******************************/
+
+static void ALn_FreeLinks (struct ALn_Link *LastLink)
+ {
+ struct ALn_Link *Link;
+ struct ALn_Link *PrevLink;
+
+ for (Link = LastLink;
+ Link;
+ Link = PrevLink)
+ {
+ PrevLink = Link->Prev;
+ if (Link->Type == ALn_LINK_NICK)
+ {
+ if (Link->Nick.Anchor3.Str) free (Link->Nick.Anchor3.Str);
+ if (Link->Nick.Anchor2.Str) free (Link->Nick.Anchor2.Str);
+ if (Link->Nick.Anchor1.Str) free (Link->Nick.Anchor1.Str);
+ }
+ free (Link);
+ }
+ }
+
+/**************************** Check if a URL found ***************************/
+
+static ALn_LinkType_t ALn_CheckURL (char **PtrSrc,char PrevCh,
+ struct ALn_Link **Link,
+ struct ALn_Link **LastLink,
+ size_t MaxCharsURLOnScreen)
+ {
+ unsigned char Ch;
+ size_t NumChars1;
+ size_t NumChars2;
+ char *PtrEnd; // Pointer to the last char of URL/nickname in original text
+ char *Limited; // URL displayed on screen (may be shorter than actual length)
+ ALn_LinkType_t Type = ALn_LINK_UNKNOWN;
+
+ /***** Check if the next char is the start of a URL *****/
+ if (tolower ((int) *(*PtrSrc)) == (int) 'h')
+ if (!Str_ChIsAlphaNum (PrevCh))
+ {
+ (*Link)->URLorNick.Str = (*PtrSrc);
+ if (tolower ((int) *++(*PtrSrc)) == (int) 't') // ht...
+ {
+ if (tolower ((int) *++(*PtrSrc)) == (int) 't') // htt...
+ {
+ if (tolower ((int) *++(*PtrSrc)) == (int) 'p') // http...
+ {
+ (*PtrSrc)++;
+ if (*(*PtrSrc) == ':') // http:...
+ {
+ if (*++(*PtrSrc) == '/') // http:/...
+ if (*++(*PtrSrc) == '/') // http://...
+ Type = ALn_LINK_URL;
+ }
+ else if (tolower ((int) *(*PtrSrc)) == (int) 's') // https...
+ {
+ if (*++(*PtrSrc) == ':') // https:...
+ {
+ if (*++(*PtrSrc) == '/') // https:/...
+ if (*++(*PtrSrc) == '/') // https://...
+ Type = ALn_LINK_URL;
+ }
+ }
+ }
+ }
+ }
+ if (Type == ALn_LINK_URL)
+ {
+ /***** Find URL end *****/
+ (*PtrSrc)++; // Points to first character after http:// or https://
+ for (;;)
+ {
+ NumChars1 = Str_GetNextASCIICharFromStr ((*PtrSrc),&Ch);
+ (*PtrSrc) += NumChars1;
+ if (Ch <= 32 || Ch == '<' || Ch == '"')
+ {
+ PtrEnd = (*PtrSrc) - NumChars1 - 1;
+ break;
+ }
+ else if (Ch == ',' || Ch == '.' || Ch == ';' || Ch == ':' ||
+ Ch == ')' || Ch == ']' || Ch == '}')
+ {
+ NumChars2 = Str_GetNextASCIICharFromStr ((*PtrSrc),&Ch);
+ (*PtrSrc) += NumChars2;
+ if (Ch <= 32 || Ch == '<' || Ch == '"')
+ {
+ PtrEnd = (*PtrSrc) - NumChars2 - NumChars1 - 1;
+ break;
+ }
+ }
+ }
+
+ /***** Compute number of bytes added until here *****/
+ (*Link)->AddedLengthUntilHere = (*Link)->Prev ? (*Link)->Prev->AddedLengthUntilHere :
+ 0;
+ (*Link)->URLorNick.Len = (size_t) (PtrEnd + 1 - (*Link)->URLorNick.Str);
+ if ((*Link)->URLorNick.Len <= MaxCharsURLOnScreen)
+ (*Link)->AddedLengthUntilHere += ALn_URL_ANCHOR_TOTAL_LENGTH +
+ (*Link)->URLorNick.Len;
+ else // If URL is too long to be displayed ==> short it
+ {
+ if ((Limited = malloc ((*Link)->URLorNick.Len + 1)) == NULL)
+ Err_NotEnoughMemoryExit ();
+ strncpy (Limited,(*Link)->URLorNick.Str,(*Link)->URLorNick.Len);
+ Limited[(*Link)->URLorNick.Len] = '\0';
+ (*Link)->AddedLengthUntilHere += ALn_URL_ANCHOR_TOTAL_LENGTH +
+ Str_LimitLengthHTMLStr (Limited,MaxCharsURLOnScreen);
+ free (Limited);
+ }
+
+ /***** Create next link *****/
+ ALn_CreateNextLink (Link,LastLink);
+ }
+ }
+
+ return Type;
+ }
+
+/************************* Check if a nickname found *************************/
+
+static ALn_LinkType_t ALn_CheckNickname (char **PtrSrc,char PrevCh,
+ struct ALn_Link **Link,
+ struct ALn_Link **LastLink)
+ {
+ extern const char *Lan_STR_LANG_ID[1 + Lan_NUM_LANGUAGES];
+ char Ch;
+ size_t Length;
+ char ParamsStr[Frm_MAX_BYTES_PARAMS_STR];
+ struct UsrData UsrDat;
+ bool ShowPhoto = false;
+ char PhotoURL[PATH_MAX + 1];
+ char *CaptionStr;
+ char *ImgStr;
+ char NickWithoutArr[Nck_MAX_BYTES_NICK_WITHOUT_ARROBA + 1];
+ bool NickSeemsValid;
+ ALn_LinkType_t Type = ALn_LINK_UNKNOWN;
+
+ /***** Check if the next char is the start of a nickname *****/
+ Ch = *(*PtrSrc);
+ if (Ch == '@') // Current is @
+ if (!Str_ChIsAlphaNum (PrevCh)) // Previous is not alphanumeric
+ if (Str_ChIsAlphaNum (*((*PtrSrc) + 1))) // Next is alphanumeric
+ {
+ (*Link)->URLorNick.Str = (*PtrSrc);
+
+ /***** Find nickname end *****/
+ (*PtrSrc)++; // Points to first character after @
+
+ /***** A nick can have digits, letters and '_' *****/
+ for (;
+ *(*PtrSrc);
+ (*PtrSrc)++)
+ {
+ Ch = *(*PtrSrc);
+ if (!Str_ChIsAlphaNum (Ch))
+ break;
+ }
+
+ /***** Calculate length of this nickname *****/
+ (*Link)->URLorNick.Len = (size_t) ((*PtrSrc) - (*Link)->URLorNick.Str);
+
+ /***** A nick (without arroba) must have a number of characters
+ Nck_MIN_CHARS_NICK_WITHOUT_ARROBA <= Length <= Nck_MAX_CHARS_NICK_WITHOUT_ARROBA *****/
+ Length = (*Link)->URLorNick.Len - 1; // Do not count the initial @
+ NickSeemsValid = Length >= Nck_MIN_CHARS_NICK_WITHOUT_ARROBA &&
+ Length <= Nck_MAX_CHARS_NICK_WITHOUT_ARROBA;
+
+ if (NickSeemsValid)
+ {
+ /***** Get user's code using nickname *****/
+ Usr_UsrDataConstructor (&UsrDat);
+ strncpy (NickWithoutArr,(*Link)->URLorNick.Str + 1,Length);
+ NickWithoutArr[Length] = '\0';
+ if ((UsrDat.UsrCod = Nck_DB_GetUsrCodFromNickname (NickWithoutArr)) > 0)
+ {
+ Type = ALn_LINK_NICK;
+ Usr_GetUsrDataFromUsrCod (&UsrDat,
+ Usr_DONT_GET_PREFS,
+ Usr_DONT_GET_ROLE_IN_CURRENT_CRS);
+ }
+
+ if (Type == ALn_LINK_NICK)
+ {
+ /***** Reset anchors (checked on freeing) *****/
+ (*Link)->Nick.Anchor1.Str =
+ (*Link)->Nick.Anchor2.Str =
+ (*Link)->Nick.Anchor3.Str = NULL;
+
+ /***** Create id for this form *****/
+ Gbl.Form.Num++;
+ if (Gbl.Usrs.Me.Logged)
+ snprintf (Gbl.Form.UniqueId,sizeof (Gbl.Form.UniqueId),
+ "form_%s_%d",Gbl.UniqueNameEncrypted,Gbl.Form.Num);
+ else
+ snprintf (Gbl.Form.Id,sizeof (Gbl.Form.Id),
+ "form_%d",Gbl.Form.Num);
+
+ /***** Store first part of anchor *****/
+ Frm_SetParamsForm (ParamsStr,ActSeeOthPubPrf,true);
+ if (asprintf (&(*Link)->Nick.Anchor1.Str,
+ "%s%s",
+ CaptionStr,
+ ImgStr) < 0)
+ Err_NotEnoughMemoryExit ();
+ free (ImgStr);
+ free (CaptionStr);
+ (*Link)->Nick.Anchor3.Len = strlen ((*Link)->Nick.Anchor3.Str);
+
+ /***** Free memory used for user's data *****/
+ Usr_UsrDataDestructor (&UsrDat);
+
+ /***** Compute number of bytes added until here *****/
+ (*Link)->AddedLengthUntilHere = (*Link)->Prev ? (*Link)->Prev->AddedLengthUntilHere :
+ 0;
+ (*Link)->AddedLengthUntilHere += (*Link)->Nick.Anchor1.Len +
+ (*Link)->URLorNick.Len +
+ (*Link)->Nick.Anchor2.Len +
+ (*Link)->Nick.Anchor3.Len;
+
+ /***** Create next link *****/
+ ALn_CreateNextLink (Link,LastLink);
+ }
+ }
+ }
+
+ return Type;
+ }
+
+/************** Copy source substring to destination, backwards **************/
+
+static void ALn_CopySubstring (const struct ALn_Substring *Src,char **Dst)
+ {
+ char *PtrSrc; // Local pointer optimizes access to memory
+ char *PtrDst; // Local pointer optimizes access to memory
+ size_t Len = Src->Len;
+ /*
+ Example: Src->Str = "Len = 9
+ Src->Str
+ |
+ _v_________________
+ |<|a|_|h|r|e|f|=|"|
+ | | | | | | | | |
+ \ \ \ \ \ \ \ \ \
+ \ \ \ \ \ \ \ \ \
+ \ \ \ \ \ \ \ \ \
+ \ \ \ \ \ \ \ \ \
+ \ \ \ \ \ \ \ \ \
+ | | | | | | | | |
+ _______v_v_v_v_v_v_v_v_v_____________
+ |_|_|_|<|a|_|h|r|e|f|=|"|_|_|_|_|_|_|
+ ^ ^
+ | |
+ PtrDst (*Dst)
+ The copy has to be done backwards to avoid overwriting the original string
+ */
+ if (Len)
+ {
+ PtrSrc = Src->Str + Len - 1;
+ PtrDst = *Dst; // Make a local copy of destination pointer
+ for (;
+ Len;
+ Len--)
+ *PtrDst-- = *PtrSrc--;
+ *Dst = PtrDst; // Update destination pointer
+ }
+ }
diff --git a/swad_autolink.h b/swad_autolink.h
new file mode 100644
index 00000000..e08779ed
--- /dev/null
+++ b/swad_autolink.h
@@ -0,0 +1,32 @@
+// swad_autolink.h: inserting automatic links in text
+
+#ifndef _SWAD_ALN
+#define _SWAD_ALN
+/*
+ SWAD (Shared Workspace At a Distance in Spanish),
+ is a web platform developed at the University of Granada (Spain),
+ and used to support university teaching.
+
+ This file is part of SWAD core.
+ Copyright (C) 1999-2021 Antonio Cañas Vargas
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+/*****************************************************************************/
+/***************************** Public prototypes ****************************/
+/*****************************************************************************/
+
+void ALn_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScreen);
+
+#endif
diff --git a/swad_changelog.h b/swad_changelog.h
index ca41e058..3d2e4d62 100644
--- a/swad_changelog.h
+++ b/swad_changelog.h
@@ -602,13 +602,14 @@ TODO: FIX BUG, URGENT! En las fechas como par
TODO: En las encuestas, que los estudiantes no puedan ver los resultados hasta que no finalice el plazo.
*/
-#define Log_PLATFORM_VERSION "SWAD 21.63 (2021-11-26)"
+#define Log_PLATFORM_VERSION "SWAD 21.64 (2021-11-28)"
#define CSS_FILE "swad21.59.css"
#define JS_FILE "swad21.59.js"
/*
TODO: Rename CENTRE to CENTER in help wiki.
TODO: Rename ASSESSMENT.Announcements to ASSESSMENT.Calls_for_exams
+ Version 21.64: Nov 28, 2021 New module swad_autolink to insert links in texts. (320076 lines)
Version 21.63: Nov 26, 2021 Fixing of corruption in test prints and match prints. (320010 lines)
Version 21.62.3: Nov 25, 2021 Fixed bug in test questions. Reported by Javier Fernández Baldomero and Jesús González Peñalver. (319438 lines)
Version 21.62.2: Nov 24, 2021 Fixed bug in forums. Reported by Javier Fernández Baldomero. (319422 lines)
diff --git a/swad_exam.c b/swad_exam.c
index 2d266cc2..24e37812 100644
--- a/swad_exam.c
+++ b/swad_exam.c
@@ -33,6 +33,7 @@
#include // For free
#include // For string functions
+#include "swad_autolink.h"
#include "swad_database.h"
#include "swad_error.h"
#include "swad_exam.h"
@@ -610,7 +611,7 @@ static void Exa_ShowOneExam (struct Exa_Exams *Exams,
Exa_DB_GetExamTxt (Exam->ExaCod,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Txt,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to rigorous HTML
- Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
HTM_DIV_Begin ("class=\"PAR %s\"",Exam->Hidden ? "DAT_LIGHT" :
"DAT");
HTM_Txt (Txt);
diff --git a/swad_exam_print.c b/swad_exam_print.c
index 247af0f2..22b0ca59 100644
--- a/swad_exam_print.c
+++ b/swad_exam_print.c
@@ -31,6 +31,7 @@
#include // For asprintf
#include // For string functions
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -634,7 +635,7 @@ static void ExaPrn_GetAndWriteDescription (long ExaCod)
Exa_DB_GetExamTxt (ExaCod,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML, // Convert from HTML to rigorous HTML
Txt,Cns_MAX_BYTES_TEXT,false);
- Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
/***** Write description *****/
HTM_DIV_Begin ("class=\"EXA_PRN_DESC DAT_SMALL\"");
diff --git a/swad_game.c b/swad_game.c
index ffb33ecc..a30ef402 100644
--- a/swad_game.c
+++ b/swad_game.c
@@ -33,6 +33,7 @@
#include // For free
#include // For string functions
+#include "swad_autolink.h"
#include "swad_database.h"
#include "swad_error.h"
#include "swad_figure.h"
@@ -661,7 +662,7 @@ static void Gam_ShowOneGame (struct Gam_Games *Games,
Gam_DB_GetGameTxt (Game->GamCod,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Txt,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to rigorous HTML
- Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
HTM_DIV_Begin ("class=\"PAR %s\"",Game->Hidden ? "DAT_LIGHT" :
"DAT");
HTM_Txt (Txt);
diff --git a/swad_global.h b/swad_global.h
index d1e23c79..d6a14cf0 100644
--- a/swad_global.h
+++ b/swad_global.h
@@ -210,7 +210,7 @@ struct Globals
struct
{
unsigned Num; // Number of institutional links
- struct Str_Link *Lst; // List of institutional links
+ struct Lnk_Link *Lst; // List of institutional links
} Links;
struct
{
diff --git a/swad_info.c b/swad_info.c
index c22386a2..5704a71f 100644
--- a/swad_info.c
+++ b/swad_info.c
@@ -34,6 +34,7 @@
#include // For unlink
#include "swad_action.h"
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -1499,7 +1500,7 @@ static bool Inf_CheckAndShowPlainTxt (void)
/***** Convert to respectful HTML and insert links *****/
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
TxtHTML,Cns_MAX_BYTES_LONG_TEXT,false); // Convert from HTML to recpectful HTML
- Str_InsertLinks (TxtHTML,Cns_MAX_BYTES_LONG_TEXT,60); // Insert links
+ ALn_InsertLinks (TxtHTML,Cns_MAX_BYTES_LONG_TEXT,60); // Insert links
/***** Write text *****/
HTM_Txt (TxtHTML);
diff --git a/swad_link.c b/swad_link.c
index 57f96b95..e3e20eaa 100644
--- a/swad_link.c
+++ b/swad_link.c
@@ -52,7 +52,7 @@ extern struct Globals Gbl;
/***************************** Private variables *****************************/
/*****************************************************************************/
-static struct Str_Link *Lnk_EditingLnk = NULL; // Static variable to keep the link being edited
+static struct Lnk_Link *Lnk_EditingLnk = NULL; // Static variable to keep the link being edited
/*****************************************************************************/
/***************************** Private prototypes ****************************/
@@ -65,7 +65,7 @@ static void Lnk_WriteListOfLinks (void);
static void Lnk_EditLinksInternal (void);
static void Lnk_PutIconsEditingLinks (__attribute__((unused)) void *Args);
-static void Lnk_GetDataOfLink (MYSQL_RES *mysql_res,struct Str_Link *Lnk);
+static void Lnk_GetDataOfLink (MYSQL_RES *mysql_res,struct Lnk_Link *Lnk);
static void Lnk_ListLinksForEdition (void);
static void Lnk_PutParamLnkCod (void *LnkCod);
@@ -309,7 +309,7 @@ void Lnk_GetListLinks (void)
/**************************** Get link full name *****************************/
/*****************************************************************************/
-void Lnk_GetDataOfLinkByCod (struct Str_Link *Lnk)
+void Lnk_GetDataOfLinkByCod (struct Lnk_Link *Lnk)
{
MYSQL_RES *mysql_res;
@@ -334,7 +334,7 @@ void Lnk_GetDataOfLinkByCod (struct Str_Link *Lnk)
/**************************** Get data of link *******************************/
/*****************************************************************************/
-static void Lnk_GetDataOfLink (MYSQL_RES *mysql_res,struct Str_Link *Lnk)
+static void Lnk_GetDataOfLink (MYSQL_RES *mysql_res,struct Lnk_Link *Lnk)
{
MYSQL_ROW row;
@@ -376,7 +376,7 @@ void Lnk_FreeListLinks (void)
static void Lnk_ListLinksForEdition (void)
{
unsigned NumLnk;
- struct Str_Link *Lnk;
+ struct Lnk_Link *Lnk;
/***** Begin table *****/
HTM_TABLE_BeginWidePadding (2);
diff --git a/swad_link.h b/swad_link.h
index 0fe2b9c5..4b743b9d 100644
--- a/swad_link.h
+++ b/swad_link.h
@@ -33,7 +33,7 @@
#define Lnk_MAX_CHARS_LINK_FULL_NAME (128 - 1) // 127
#define Lnk_MAX_BYTES_LINK_FULL_NAME ((Lnk_MAX_CHARS_LINK_FULL_NAME + 1) * Str_MAX_BYTES_PER_CHAR - 1) // 2047
-struct Str_Link
+struct Lnk_Link
{
long LnkCod;
char ShrtName[Lnk_MAX_BYTES_LINK_SHRT_NAME + 1];
@@ -54,7 +54,7 @@ void Lnk_PutIconToViewLinks (void);
void Lnk_GetListLinks (void);
void Lnk_FreeListLinks (void);
-void Lnk_GetDataOfLinkByCod (struct Str_Link *Lnk);
+void Lnk_GetDataOfLinkByCod (struct Lnk_Link *Lnk);
long Lnk_GetParamLnkCod (void);
void Lnk_RemoveLink (void);
void Lnk_RenameLinkShort (void);
diff --git a/swad_link_database.c b/swad_link_database.c
index 0ee9cae3..b6bfc202 100644
--- a/swad_link_database.c
+++ b/swad_link_database.c
@@ -32,7 +32,7 @@
/**************************** Create a new link ******************************/
/*****************************************************************************/
-void Lnk_DB_CreateLink (const struct Str_Link *Lnk)
+void Lnk_DB_CreateLink (const struct Lnk_Link *Lnk)
{
DB_QueryINSERT ("can not create institutional link",
"INSERT INTO lnk_links"
diff --git a/swad_link_database.h b/swad_link_database.h
index 0fcd52e7..00cb349b 100644
--- a/swad_link_database.h
+++ b/swad_link_database.h
@@ -35,7 +35,7 @@
/****************************** Public prototypes ****************************/
/*****************************************************************************/
-void Lnk_DB_CreateLink (const struct Str_Link *Lnk);
+void Lnk_DB_CreateLink (const struct Lnk_Link *Lnk);
void Lnk_DB_UpdateLnkName (long LnkCod,const char *FieldName,const char *NewLnkName);
void Lnk_DB_UpdateLnkWWW (long LnkCod,const char NewWWW[Cns_MAX_BYTES_WWW + 1]);
diff --git a/swad_message.c b/swad_message.c
index 8a29707d..ed51e08e 100644
--- a/swad_message.c
+++ b/swad_message.c
@@ -34,6 +34,7 @@
#include // For time
#include "swad_action.h"
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_config.h"
#include "swad_course.h"
@@ -2687,7 +2688,7 @@ void Msg_WriteMsgContent (char Content[Cns_MAX_BYTES_LONG_TEXT + 1],
{
/***** Insert links in URLs *****/
if (InsertLinks)
- Str_InsertLinks (Content,Cns_MAX_BYTES_LONG_TEXT,60);
+ ALn_InsertLinks (Content,Cns_MAX_BYTES_LONG_TEXT,60);
/***** Write message to file *****/
if (ChangeBRToRet)
diff --git a/swad_notice.c b/swad_notice.c
index b9bcd84e..2a7d363d 100644
--- a/swad_notice.c
+++ b/swad_notice.c
@@ -32,6 +32,7 @@
#include // For free
#include
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -380,7 +381,7 @@ void Not_ShowNotices (Not_Listing_t TypeNoticesListing,long HighlightNotCod)
Str_LimitLengthHTMLStr (Content,Not_MAX_CHARS_ON_NOTICE);
break;
case Not_LIST_FULL_NOTICES:
- Str_InsertLinks (Content,Cns_MAX_BYTES_TEXT,
+ ALn_InsertLinks (Content,Cns_MAX_BYTES_TEXT,
Not_MaxCharsURLOnScreen[TypeNoticesListing]);
break;
}
@@ -511,7 +512,7 @@ static void Not_GetDataAndShowNotice (long NotCod)
/* Get the content (row[2]) and insert links*/
Str_Copy (Content,row[2],sizeof (Content) - 1);
- Str_InsertLinks (Content,Cns_MAX_BYTES_TEXT,
+ ALn_InsertLinks (Content,Cns_MAX_BYTES_TEXT,
Not_MaxCharsURLOnScreen[Not_LIST_FULL_NOTICES]);
/* Get status of the notice (row[3]) */
diff --git a/swad_program.c b/swad_program.c
index 4c757fce..a20b35e8 100644
--- a/swad_program.c
+++ b/swad_program.c
@@ -32,6 +32,7 @@
#include // For calloc
#include // For string functions
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -439,7 +440,7 @@ static void Prg_WriteRowItem (unsigned NumItem,struct Prg_Item *Item,
Prg_DB_GetItemTxt (Item->Hierarchy.ItmCod,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Txt,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to recpectful HTML
- Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
HTM_DIV_Begin ("class=\"PAR PRG_TXT%s\"",
LightStyle ? "PRG_HIDDEN" :
"");
diff --git a/swad_project.c b/swad_project.c
index c3f85793..6b31c4e9 100644
--- a/swad_project.c
+++ b/swad_project.c
@@ -32,6 +32,7 @@
#include // For calloc
#include // For string functions
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_browser_database.h"
#include "swad_database.h"
@@ -1857,7 +1858,7 @@ static void Prj_ShowOneProjectTxtField (struct Prj_Project *Prj,
{
case Prj_LIST_PROJECTS:
case Prj_FILE_BROWSER_PROJECT:
- Str_InsertLinks (TxtField,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (TxtField,Cns_MAX_BYTES_TEXT,60); // Insert links
break;
default:
break;
diff --git a/swad_question_database.c b/swad_question_database.c
index 49ae5f07..e0615a90 100644
--- a/swad_question_database.c
+++ b/swad_question_database.c
@@ -1378,7 +1378,7 @@ unsigned Qst_DB_GetTextOfAnswers (MYSQL_RES **mysql_res,long QstCod)
}
/*****************************************************************************/
-/********** Get answers correctness for a question in an exam set ************/
+/*************** Get answers correctness for a question **********************/
/*****************************************************************************/
unsigned Qst_DB_GetQstAnswersCorr (MYSQL_RES **mysql_res,long QstCod)
diff --git a/swad_string.c b/swad_string.c
index 7802c705..1bb72285 100644
--- a/swad_string.c
+++ b/swad_string.c
@@ -29,79 +29,24 @@
#include // For isprint, isspace, etc.
#include // For setlocale
#include // For log10, floor, ceil, modf, sqrt...
-#include // For NULL
#include // For asprintf
-#include // For malloc and free
#include // For string functions
#include "swad_error.h"
-#include "swad_form.h"
#include "swad_global.h"
-#include "swad_ID.h"
-#include "swad_nickname_database.h"
#include "swad_notification_database.h"
-#include "swad_parameter.h"
-#include "swad_photo.h"
#include "swad_string.h"
-/*****************************************************************************/
-/******************************* Private types *******************************/
-/*****************************************************************************/
-
-typedef enum
- {
- Str_LINK_UNKNOWN = 0,
- Str_LINK_URL = 1,
- Str_LINK_NICK = 2,
- } Str_LinkType_t;
-
-struct Str_Substring
- {
- char *Str; // Pointer to the first char of substring
- size_t Len; // Length of the substring
- };
-
-struct Str_Link
- {
- Str_LinkType_t Type;
- struct Str_Substring URLorNick;
- struct
- {
- struct Str_Substring Anchor1;
- struct Str_Substring Anchor2;
- struct Str_Substring Anchor3;
- } Nick;
- size_t AddedLengthUntilHere; // Total length of extra HTML code added until this link (included)
- struct Str_Link *Prev;
- struct Str_Link *Next;
- };
-
/*****************************************************************************/
/******************** Global variables from other modules ********************/
/*****************************************************************************/
-extern struct Globals Gbl; // Declaration in swad.c
+extern struct Globals Gbl;
/*****************************************************************************/
/*************************** Private prototypes ******************************/
/*****************************************************************************/
-static void Str_CreateFirstLink (struct Str_Link **Link,
- struct Str_Link **LastLink);
-static void Str_CreateNextLink (struct Str_Link **Link,
- struct Str_Link **LastLink);
-static void Str_FreeLinks (struct Str_Link *LastLink);
-static Str_LinkType_t Str_CheckURL (char **PtrSrc,char PrevCh,
- struct Str_Link **Link,
- struct Str_Link **LastLink,
- size_t MaxCharsURLOnScreen);
-static Str_LinkType_t Str_CheckNickname (char **PtrSrc,char PrevCh,
- struct Str_Link **Link,
- struct Str_Link **LastLink);
-static void Str_CopySubstring (const struct Str_Substring *PtrSrc,char **PtrDst);
-
-static unsigned Str_GetNextASCIICharFromStr (const char *Ptr,unsigned char *Ch);
-
static unsigned Str_FindHTMLEntity (const char *Ptr);
static int Str_ReadCharAndSkipComments (FILE *FileSrc,Str_SkipHTMLComments_t SkipHTMLComments);
@@ -124,526 +69,6 @@ const char Str_BIN_TO_BASE64URL[64 + 1] =
static const char Str_LF[2] = {10,0};
static const char Str_CR[2] = {13,0};
-/*****************************************************************************/
-/****************** Insert a link in every URL or nickname *******************/
-/*****************************************************************************/
-/*
-Insertion example:
-The web site of @rms is https://stallman.org/
-The web site of @rms is https://stallman.org/
-*/
-
-/*
-
-
-
-*/
-
-// For URLs the length of anchor is fixed, so it can be calculated once
-#define URL_ANCHOR_1 ""
-#define URL_ANCHOR_3 ""
-#define URL_ANCHOR_1_LENGTH (sizeof (URL_ANCHOR_1) - 1)
-#define URL_ANCHOR_2_LENGTH (sizeof (URL_ANCHOR_2) - 1)
-#define URL_ANCHOR_3_LENGTH (sizeof (URL_ANCHOR_3) - 1)
-#define URL_ANCHOR_TOTAL_LENGTH (URL_ANCHOR_1_LENGTH + URL_ANCHOR_2_LENGTH + URL_ANCHOR_3_LENGTH)
-
-// For nicknames the length of anchor is variable,
-// so it can be calculated for each link,
-// except the third part that is fixed
-/*
-#define NICK_ANCHOR_3 ""
-#define NICK_ANCHOR_3_LENGTH (sizeof (NICK_ANCHOR_3) - 1)
-*/
-static const struct Str_Substring URLAnchor1 =
- {
- .Str = URL_ANCHOR_1,
- .Len = URL_ANCHOR_1_LENGTH,
- };
-static const struct Str_Substring URLAnchor2 =
- {
- .Str = URL_ANCHOR_2,
- .Len = URL_ANCHOR_2_LENGTH,
- };
-static const struct Str_Substring URLAnchor3 =
- {
- .Str = URL_ANCHOR_3,
- .Len = URL_ANCHOR_3_LENGTH,
- };
-/*
-static const struct Str_Substring NickAnchor3 =
- {
- .Str = NICK_ANCHOR_3,
- .Len = NICK_ANCHOR_3_LENGTH,
- };
-*/
-void Str_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScreen)
- {
- size_t TxtLength;
- char PrevCh = '\0';
- char *PtrSrc;
- char *PtrDst;
- struct Str_Link *Link;
- struct Str_Link *LastLink;
- size_t Length;
- size_t i;
- struct Str_Substring Limited; // URL displayed on screen (may be shorter than actual length)
- const struct Str_Substring *Anchor1;
- const struct Str_Substring *Anchor2;
- const struct Str_Substring *Anchor3;
-
- /**************************************************************/
- /***** Find starts and ends of links (URLs and nicknames) *****/
- /**************************************************************/
- Str_CreateFirstLink (&Link,&LastLink);
-
- for (PtrSrc = Txt;
- *PtrSrc;)
- /* Check if the next char is the start of a URL */
- if ((Link->Type = Str_CheckURL (&PtrSrc,PrevCh,
- &Link,&LastLink,
- MaxCharsURLOnScreen)) == Str_LINK_UNKNOWN)
- /* Check if the next char is the start of a nickname */
- if ((Link->Type = Str_CheckNickname (&PtrSrc,PrevCh,
- &Link,&LastLink)) == Str_LINK_UNKNOWN)
- {
- /* The next char is not the start of a URL or a nickname */
- PrevCh = *PtrSrc;
- PtrSrc++;
- }
-
- /**********************************************************************/
- /***** If there are one or more links (URLs or nicknames) in text *****/
- /**********************************************************************/
- if (LastLink) // Not null ==> one or more links found
- {
- /***** Insert links from end to start of text,
- only if there is enough space available in text *****/
- TxtLength = strlen (Txt);
- if (TxtLength + LastLink->AddedLengthUntilHere <= MaxLength)
- for (Link = LastLink;
- Link;
- Link = Link->Prev)
- {
- /***** Set anchors *****/
- switch (Link->Type)
- {
- case Str_LINK_URL:
- Anchor1 = &URLAnchor1;
- Anchor2 = &URLAnchor2;
- Anchor3 = &URLAnchor3;
- break;
- case Str_LINK_NICK:
- Anchor1 = &Link->Nick.Anchor1;
- Anchor2 = &Link->Nick.Anchor2;
- Anchor3 = &Link->Nick.Anchor3;
- break;
- default:
- continue;
- }
-
- /***** Step 1: Move forward the text after the link (URL or nickname)
- (it's mandatory to do the copy in reverse order
- to avoid overwriting source) *****/
- PtrSrc = (Link == LastLink) ? Txt + TxtLength :
- Link->Next->URLorNick.Str - 1,
- PtrDst = PtrSrc + Link->AddedLengthUntilHere,
- Length = PtrSrc - (Link->URLorNick.Str + Link->URLorNick.Len - 1);
- for (i = 0;
- i < Length;
- i++)
- *PtrDst-- = *PtrSrc--;
-
- /***** Step 2: Copy third part of anchor *****/
- Str_CopySubstring (Anchor3,&PtrDst);
-
- /***** Step 3: Move forward the link (URL or nickname)
- to be shown on screen *****/
- switch (Link->Type)
- {
- case Str_LINK_URL:
- if (Link->URLorNick.Len <= MaxCharsURLOnScreen)
- Str_CopySubstring (&Link->URLorNick,&PtrDst);
- else
- {
- /* Limit the length of URL */
- if ((Limited.Str = malloc (Link->URLorNick.Len + 1)) == NULL)
- Err_NotEnoughMemoryExit ();
- strncpy (Limited.Str,Link->URLorNick.Str,Link->URLorNick.Len);
- Limited.Str[Link->URLorNick.Len] = '\0';
- Limited.Len = Str_LimitLengthHTMLStr (Limited.Str,MaxCharsURLOnScreen);
- Str_CopySubstring (&Limited,&PtrDst);
- free (Limited.Str);
- }
- break;
- case Str_LINK_NICK:
- Str_CopySubstring (&Link->URLorNick,&PtrDst);
- break;
- default:
- break;
- }
-
- /***** Step 4: Copy second part of anchor *****/
- Str_CopySubstring (Anchor2,&PtrDst);
-
- /***** Step 5: Copy link into directive A
- (it's mandatory to do the copy in reverse order
- to avoid overwriting source URL or nickname) *****/
- Str_CopySubstring (&Link->URLorNick,&PtrDst);
-
- /***** Step 6: Copy first part of anchor *****/
- Str_CopySubstring (Anchor1,&PtrDst);
- }
- }
-
- /***********************************/
- /***** Free memory for anchors *****/
- /***********************************/
- Str_FreeLinks (LastLink);
- }
-
-/***************************** Create first link ******************************/
-
-static void Str_CreateFirstLink (struct Str_Link **Link,
- struct Str_Link **LastLink)
- {
- /***** Reset last link pointer *****/
- (*LastLink) = NULL;
-
- /***** Allocate current link *****/
- if (((*Link) = malloc (sizeof (struct Str_Link))) == NULL)
- Err_NotEnoughMemoryExit ();
-
- /***** Initialize current link *****/
- (*Link)->Prev = NULL;
- (*Link)->Next = NULL;
- }
-
-/***************************** Create next link ******************************/
-
-static void Str_CreateNextLink (struct Str_Link **Link,
- struct Str_Link **LastLink)
- {
- /***** Current link now is pointing to a correct link,
- so set last link pointer to current link *****/
- (*LastLink) = (*Link);
-
- /***** Allocate next link *****/
- if (((*Link)->Next = malloc (sizeof (struct Str_Link))) == NULL)
- Err_NotEnoughMemoryExit ();
-
- /***** Initialize next link *****/
- (*Link)->Next->Prev = *Link;
- (*Link)->Next->Next = NULL;
-
- /***** Change current link to just allocated link *****/
- (*Link) = (*Link)->Next;
- }
-
-/***************************** Free found links ******************************/
-
-static void Str_FreeLinks (struct Str_Link *LastLink)
- {
- struct Str_Link *Link;
- struct Str_Link *PrevLink;
-
- for (Link = LastLink;
- Link;
- Link = PrevLink)
- {
- PrevLink = Link->Prev;
- if (Link->Type == Str_LINK_NICK)
- {
- if (Link->Nick.Anchor3.Str) free (Link->Nick.Anchor3.Str);
- if (Link->Nick.Anchor2.Str) free (Link->Nick.Anchor2.Str);
- if (Link->Nick.Anchor1.Str) free (Link->Nick.Anchor1.Str);
- }
- free (Link);
- }
- }
-
-/**************************** Check if a URL found ***************************/
-
-static Str_LinkType_t Str_CheckURL (char **PtrSrc,char PrevCh,
- struct Str_Link **Link,
- struct Str_Link **LastLink,
- size_t MaxCharsURLOnScreen)
- {
- unsigned char Ch;
- size_t NumChars1;
- size_t NumChars2;
- char *PtrEnd; // Pointer to the last char of URL/nickname in original text
- char *Limited; // URL displayed on screen (may be shorter than actual length)
- Str_LinkType_t Type = Str_LINK_UNKNOWN;
-
- /***** Check if the next char is the start of a URL *****/
- if (tolower ((int) *(*PtrSrc)) == (int) 'h')
- if (!Str_ChIsAlphaNum (PrevCh))
- {
- (*Link)->URLorNick.Str = (*PtrSrc);
- if (tolower ((int) *++(*PtrSrc)) == (int) 't') // ht...
- {
- if (tolower ((int) *++(*PtrSrc)) == (int) 't') // htt...
- {
- if (tolower ((int) *++(*PtrSrc)) == (int) 'p') // http...
- {
- (*PtrSrc)++;
- if (*(*PtrSrc) == ':') // http:...
- {
- if (*++(*PtrSrc) == '/') // http:/...
- if (*++(*PtrSrc) == '/') // http://...
- Type = Str_LINK_URL;
- }
- else if (tolower ((int) *(*PtrSrc)) == (int) 's') // https...
- {
- if (*++(*PtrSrc) == ':') // https:...
- {
- if (*++(*PtrSrc) == '/') // https:/...
- if (*++(*PtrSrc) == '/') // https://...
- Type = Str_LINK_URL;
- }
- }
- }
- }
- }
- if (Type == Str_LINK_URL)
- {
- /***** Find URL end *****/
- (*PtrSrc)++; // Points to first character after http:// or https://
- for (;;)
- {
- NumChars1 = Str_GetNextASCIICharFromStr ((*PtrSrc),&Ch);
- (*PtrSrc) += NumChars1;
- if (Ch <= 32 || Ch == '<' || Ch == '"')
- {
- PtrEnd = (*PtrSrc) - NumChars1 - 1;
- break;
- }
- else if (Ch == ',' || Ch == '.' || Ch == ';' || Ch == ':' ||
- Ch == ')' || Ch == ']' || Ch == '}')
- {
- NumChars2 = Str_GetNextASCIICharFromStr ((*PtrSrc),&Ch);
- (*PtrSrc) += NumChars2;
- if (Ch <= 32 || Ch == '<' || Ch == '"')
- {
- PtrEnd = (*PtrSrc) - NumChars2 - NumChars1 - 1;
- break;
- }
- }
- }
-
- /***** Compute number of bytes added until here *****/
- (*Link)->AddedLengthUntilHere = (*Link)->Prev ? (*Link)->Prev->AddedLengthUntilHere :
- 0;
- (*Link)->URLorNick.Len = (size_t) (PtrEnd + 1 - (*Link)->URLorNick.Str);
- if ((*Link)->URLorNick.Len <= MaxCharsURLOnScreen)
- (*Link)->AddedLengthUntilHere += URL_ANCHOR_TOTAL_LENGTH +
- (*Link)->URLorNick.Len;
- else // If URL is too long to be displayed ==> short it
- {
- if ((Limited = malloc ((*Link)->URLorNick.Len + 1)) == NULL)
- Err_NotEnoughMemoryExit ();
- strncpy (Limited,(*Link)->URLorNick.Str,(*Link)->URLorNick.Len);
- Limited[(*Link)->URLorNick.Len] = '\0';
- (*Link)->AddedLengthUntilHere += URL_ANCHOR_TOTAL_LENGTH +
- Str_LimitLengthHTMLStr (Limited,MaxCharsURLOnScreen);
- free (Limited);
- }
-
- /***** Create next link *****/
- Str_CreateNextLink (Link,LastLink);
- }
- }
-
- return Type;
- }
-
-/************************* Check if a nickname found *************************/
-
-static Str_LinkType_t Str_CheckNickname (char **PtrSrc,char PrevCh,
- struct Str_Link **Link,
- struct Str_Link **LastLink)
- {
- extern const char *Lan_STR_LANG_ID[1 + Lan_NUM_LANGUAGES];
- char Ch;
- size_t Length;
- char ParamsStr[Frm_MAX_BYTES_PARAMS_STR];
- struct UsrData UsrDat;
- bool ShowPhoto = false;
- char PhotoURL[PATH_MAX + 1];
- char *CaptionStr;
- char *ImgStr;
- char NickWithoutArr[Nck_MAX_BYTES_NICK_WITHOUT_ARROBA + 1];
- bool NickSeemsValid;
- Str_LinkType_t Type = Str_LINK_UNKNOWN;
-
- /***** Check if the next char is the start of a nickname *****/
- Ch = *(*PtrSrc);
- if (Ch == '@') // Current is @
- if (!Str_ChIsAlphaNum (PrevCh)) // Previous is not alphanumeric
- if (Str_ChIsAlphaNum (*((*PtrSrc) + 1))) // Next is alphanumeric
- {
- (*Link)->URLorNick.Str = (*PtrSrc);
-
- /***** Find nickname end *****/
- (*PtrSrc)++; // Points to first character after @
-
- /***** A nick can have digits, letters and '_' *****/
- for (;
- *(*PtrSrc);
- (*PtrSrc)++)
- {
- Ch = *(*PtrSrc);
- if (!Str_ChIsAlphaNum (Ch))
- break;
- }
-
- /***** Calculate length of this nickname *****/
- (*Link)->URLorNick.Len = (size_t) ((*PtrSrc) - (*Link)->URLorNick.Str);
-
- /***** A nick (without arroba) must have a number of characters
- Nck_MIN_CHARS_NICK_WITHOUT_ARROBA <= Length <= Nck_MAX_CHARS_NICK_WITHOUT_ARROBA *****/
- Length = (*Link)->URLorNick.Len - 1; // Do not count the initial @
- NickSeemsValid = Length >= Nck_MIN_CHARS_NICK_WITHOUT_ARROBA &&
- Length <= Nck_MAX_CHARS_NICK_WITHOUT_ARROBA;
-
- if (NickSeemsValid)
- {
- /***** Get user's code using nickname *****/
- Usr_UsrDataConstructor (&UsrDat);
- strncpy (NickWithoutArr,(*Link)->URLorNick.Str + 1,Length);
- NickWithoutArr[Length] = '\0';
- if ((UsrDat.UsrCod = Nck_DB_GetUsrCodFromNickname (NickWithoutArr)) > 0)
- {
- Type = Str_LINK_NICK;
- Usr_GetUsrDataFromUsrCod (&UsrDat,
- Usr_DONT_GET_PREFS,
- Usr_DONT_GET_ROLE_IN_CURRENT_CRS);
- }
-
- if (Type == Str_LINK_NICK)
- {
- /***** Reset anchors (checked on freeing) *****/
- (*Link)->Nick.Anchor1.Str =
- (*Link)->Nick.Anchor2.Str =
- (*Link)->Nick.Anchor3.Str = NULL;
-
- /***** Create id for this form *****/
- Gbl.Form.Num++;
- if (Gbl.Usrs.Me.Logged)
- snprintf (Gbl.Form.UniqueId,sizeof (Gbl.Form.UniqueId),
- "form_%s_%d",Gbl.UniqueNameEncrypted,Gbl.Form.Num);
- else
- snprintf (Gbl.Form.Id,sizeof (Gbl.Form.Id),
- "form_%d",Gbl.Form.Num);
-
- /***** Store first part of anchor *****/
- Frm_SetParamsForm (ParamsStr,ActSeeOthPubPrf,true);
- if (asprintf (&(*Link)->Nick.Anchor1.Str,
- "%s%s",
- CaptionStr,
- ImgStr) < 0)
- Err_NotEnoughMemoryExit ();
- free (ImgStr);
- free (CaptionStr);
- (*Link)->Nick.Anchor3.Len = strlen ((*Link)->Nick.Anchor3.Str);
-
- /***** Free memory used for user's data *****/
- Usr_UsrDataDestructor (&UsrDat);
-
- /***** Compute number of bytes added until here *****/
- (*Link)->AddedLengthUntilHere = (*Link)->Prev ? (*Link)->Prev->AddedLengthUntilHere :
- 0;
- (*Link)->AddedLengthUntilHere += (*Link)->Nick.Anchor1.Len +
- (*Link)->URLorNick.Len +
- (*Link)->Nick.Anchor2.Len +
- (*Link)->Nick.Anchor3.Len;
-
- /***** Create next link *****/
- Str_CreateNextLink (Link,LastLink);
- }
- }
- }
-
- return Type;
- }
-
-/************** Copy source substring to destination, backwards **************/
-
-static void Str_CopySubstring (const struct Str_Substring *Src,char **Dst)
- {
- char *PtrSrc; // Local pointer optimizes access to memory
- char *PtrDst; // Local pointer optimizes access to memory
- size_t Len = Src->Len;
- /*
- Example: Src->Str = "Len = 9
- Src->Str
- |
- _v_________________
- |<|a|_|h|r|e|f|=|"|
- | | | | | | | | |
- \ \ \ \ \ \ \ \ \
- \ \ \ \ \ \ \ \ \
- \ \ \ \ \ \ \ \ \
- \ \ \ \ \ \ \ \ \
- \ \ \ \ \ \ \ \ \
- | | | | | | | | |
- _______v_v_v_v_v_v_v_v_v_____________
- |_|_|_|<|a|_|h|r|e|f|=|"|_|_|_|_|_|_|
- ^ ^
- | |
- PtrDst (*Dst)
- The copy has to be done backwards to avoid overwriting the original string
- */
- if (Len)
- {
- PtrSrc = Src->Str + Len - 1;
- PtrDst = *Dst; // Make a local copy of destination pointer
- for (;
- Len;
- Len--)
- *PtrDst-- = *PtrSrc--;
- *Dst = PtrDst; // Update destination pointer
- }
- }
-
/*****************************************************************************/
/*********** Check if a character is in set { a-z, A-Z, 0-9, _ } *************/
/*****************************************************************************/
@@ -661,7 +86,7 @@ bool Str_ChIsAlphaNum (char Ch)
/*****************************************************************************/
// Returns number of char analyzed
-static unsigned Str_GetNextASCIICharFromStr (const char *Ptr,unsigned char *Ch)
+unsigned Str_GetNextASCIICharFromStr (const char *Ptr,unsigned char *Ch)
{
unsigned NumChars;
unsigned Num;
diff --git a/swad_string.h b/swad_string.h
index 1cdfbba1..1e7608fa 100644
--- a/swad_string.h
+++ b/swad_string.h
@@ -82,10 +82,10 @@ typedef enum
/***************************** Public prototypes ****************************/
/*****************************************************************************/
-void Str_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScreen);
-
bool Str_ChIsAlphaNum (char Ch);
+unsigned Str_GetNextASCIICharFromStr (const char *Ptr,unsigned char *Ch);
+
size_t Str_LimitLengthHTMLStr (char *Str,size_t MaxCharsOnScreen);
void Str_AnalyzeTxtAndStoreNotifyEventToMentionedUsrs (long PubCod,const char *Txt);
diff --git a/swad_survey.c b/swad_survey.c
index fd3f884a..cf659fa5 100644
--- a/swad_survey.c
+++ b/swad_survey.c
@@ -33,6 +33,7 @@
#include // For string functions
#include "swad_attendance.h"
+#include "swad_autolink.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
@@ -669,7 +670,7 @@ static void Svy_ShowOneSurvey (struct Svy_Surveys *Surveys,
Svy_DB_GetSurveyTxt (Svy.SvyCod,Txt);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
Txt,Cns_MAX_BYTES_TEXT,false); // Convert from HTML to rigorous HTML
- Str_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
+ ALn_InsertLinks (Txt,Cns_MAX_BYTES_TEXT,60); // Insert links
HTM_DIV_Begin ("class=\"PAR %s\"",Svy.Status.Visible ? "DAT" :
"DAT_LIGHT");
HTM_Txt (Txt);