From c4ab84c2c74f8688346c45ac902e79fbe3a07a6d Mon Sep 17 00:00:00 2001 From: acanas Date: Sun, 28 Nov 2021 00:49:23 +0100 Subject: [PATCH] Version 21.64: Nov 28, 2021 New module swad_autolink to insert links in texts. --- Makefile | 2 +- swad_API.c | 3 +- swad_RSS.c | 3 +- swad_agenda.c | 3 +- swad_announcement.c | 5 +- swad_assignment.c | 3 +- swad_attendance.c | 3 +- swad_autolink.c | 611 +++++++++++++++++++++++++++++++++++++++ swad_autolink.h | 32 ++ swad_changelog.h | 3 +- swad_exam.c | 3 +- swad_exam_print.c | 3 +- swad_game.c | 3 +- swad_global.h | 2 +- swad_info.c | 3 +- swad_link.c | 10 +- swad_link.h | 4 +- swad_link_database.c | 2 +- swad_link_database.h | 2 +- swad_message.c | 3 +- swad_notice.c | 5 +- swad_program.c | 3 +- swad_project.c | 3 +- swad_question_database.c | 2 +- swad_string.c | 579 +------------------------------------ swad_string.h | 4 +- swad_survey.c | 3 +- 27 files changed, 693 insertions(+), 609 deletions(-) create mode 100644 swad_autolink.c create mode 100644 swad_autolink.h 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/ +*/ + +/* + +
+ + + +@acanas + +
+ +*/ + +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" // Parameters + "Nick.Anchor1.Len = strlen ((*Link)->Nick.Anchor1.Str); + + /***** Store second part of anchor *****/ + if (asprintf (&(*Link)->Nick.Anchor2.Str, + "\">" + "", + Gbl.Usrs.Me.Logged ? Gbl.Form.UniqueId : + Gbl.Form.Id) < 0) + Err_NotEnoughMemoryExit (); + (*Link)->Nick.Anchor2.Len = strlen ((*Link)->Nick.Anchor2.Str); + + /***** Store third part of anchor *****/ + ShowPhoto = Pho_ShowingUsrPhotoIsAllowed (&UsrDat,PhotoURL); + Pho_BuildHTMLUsrPhoto (&UsrDat,ShowPhoto ? PhotoURL : + NULL, + "PHOTO12x16",Pho_ZOOM, + &CaptionStr, + &ImgStr); + if (asprintf (&(*Link)->Nick.Anchor3.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/ -*/ - -/* - -
- - - -@acanas - -
- -*/ - -// 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" // Parameters - "Nick.Anchor1.Len = strlen ((*Link)->Nick.Anchor1.Str); - - /***** Store second part of anchor *****/ - if (asprintf (&(*Link)->Nick.Anchor2.Str, - "\">" - "", - Gbl.Usrs.Me.Logged ? Gbl.Form.UniqueId : - Gbl.Form.Id) < 0) - Err_NotEnoughMemoryExit (); - (*Link)->Nick.Anchor2.Len = strlen ((*Link)->Nick.Anchor2.Str); - - /***** Store third part of anchor *****/ - ShowPhoto = Pho_ShowingUsrPhotoIsAllowed (&UsrDat,PhotoURL); - Pho_BuildHTMLUsrPhoto (&UsrDat,ShowPhoto ? PhotoURL : - NULL, - "PHOTO12x16",Pho_ZOOM, - &CaptionStr, - &ImgStr); - if (asprintf (&(*Link)->Nick.Anchor3.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);