在寫聊天系統的時候,不可避免地要對聊天系統中的訊息做一些解析
常見的比如一句話中帶有emoji、link等資訊的時候,要把emoji解析成圖片、把link轉成可以點選的
(專案中沒有做對圖片做行內處理,而是把圖片像微信一樣作為單獨訊息傳送)
我們知道react的標籤都是jsx的,所以在解析訊息的時候,就必須在得到訊息內容的時候,就先把訊息內容分段擷取
比如這樣一則訊息
今天吃飯了嗎?[emoji]我還沒吃呢[emoji],給你個連結看看吧!http://www.google.com/
emoji要解析成圖片,http://www.google.com/ 要解析成可以點選的連結,之間的文字要解析成文字
jquery時代,只需要使用正則匹配emoji,替換成圖片,用正則匹配連結,替換成a標籤即可
但是在react裡,這三者對應的是不同的jsx標籤。所以必須把文字解析成分段式的
思路:
上面這句話,可以解析成6部分
part1: 今天吃飯了嗎?
part2: [emoji]
part3: 我還沒吃呢
part4: [emoji]
part5: ,給你個連結看看吧!
part6: http://www.google.com/
每部分對應使用不同的jsx標籤
第一步,我們先使用正則匹配emoji和連結
分別的正則如下
(匹配連結應該有更優秀的正則)
var emojiregex = new RegExp(/ud83c[udf00-udfff]|ud83d[udc00-ude4f]|ud83d[ude80-udeff]/g, `g`); // 匹配emoji字元
var matchUrlRegex = new RegExp(/(https?:)//([^/]+)(/[^?]*)?(?[^#]*)?(#.*)?/g,`g`); // 匹配url的正則
var emojiRegArray = text.match(emojiregex); // 匹配了所有的emoji的詞
var urlRegArray = text.match(matchUrlRegex);
得到兩個陣列,分別是匹配到的emoji和匹配到的url
第二步,使用index()方法,獲取每個emoji、url的位置、長度,並記錄
var indexEmojiArray = []; // 記錄表情的位置、內容的陣列
var indexUrlArray = []; // 記錄連結的位置、內容的陣列
var pos1 = -1, pos2 = -1;//頭
if(emojiRegArray){
for (let i = 0; i < emojiRegArray.length; i++) {
pos1 = text.indexOf(emojiRegArray[i], pos1 + 1);
indexEmojiArray.push({
type: 1, // type為1表示是表情
pos: pos1,
length: emojiRegArray[i].length,
res: emojiRegArray[i],
});
}
}
if(urlRegArray){
for (let i = 0; i < urlRegArray.length; i++) {
pos2 = text.indexOf(urlRegArray[i], pos2 + 1);
indexUrlArray.push({
type: 3, // type為1表示是url
pos: pos2,
length: urlRegArray[i].length,
res: urlRegArray[i],
});
}
}
第三步,按照這些元素在訊息中的位置,兩個陣列合併成一個陣列
// 合併兩個陣列
var indexArray = []; // 以上兩個陣列按照pos順序合併的陣列
if(emojiRegArray && urlRegArray){
let point1 = 0,point2 = 0;
while(point1 < indexEmojiArray.length || point2 < indexUrlArray.length){
if(!indexEmojiArray[point1]){ // emoji加完了
indexArray.push(indexUrlArray[point2]);
point2++;
}else if(!indexUrlArray[point2]){// url加完了
indexArray.push(indexEmojiArray[point1]);
point1++;
}else{ // 兩個都沒加完
if(indexEmojiArray[point1].pos < indexUrlArray[point2].pos){
indexArray.push(indexEmojiArray[point1]);
point1++;
}else{
indexArray.push(indexUrlArray[point2]);
point2++;
}
}
}
}else if(emojiRegArray && !urlRegArray){ // 有emoji沒有url
indexArray = indexEmojiArray;
}else if(!emojiRegArray && urlRegArray){ // 有url沒有emoji
indexArray = indexUrlArray;
}
第四步
現在,我們得到了一個indexArray,儲存了emoji和url的位置和長度的陣列
現在我們要把文字也加進去,並且,emoji替換成圖片
// 這裡開始把indexArray加工成contentArray
let contentArray = [];
let point = 0; // 記錄當前指標位置
for (let i = 0; i < indexArray.length; i++) {
// 把這一項和上一項之間的內容push成文字
console.log(point);
let textContent = text.substr(point, indexArray[i].pos-point);
console.log(textContent);
contentArray.push({type: 0, content: textContent});
// point += textContent.length;
if(indexArray[i].type === 1){ // 如果這一項是emoji
// contentArray.push({ type: 1, "resources": EMOJI_MAP[indexArray[i].res] || [] });
contentArray.push({ type: 1, resources: indexArray[i].res || [] });
point = indexArray[i].pos + indexArray[i].length;
}else if(indexArray[i].type === 3){ // 如果這一項是url
contentArray.push({ type: 3, url: indexArray[i].res});
point = indexArray[i].pos + indexArray[i].length;
}
}
// 加入末尾項。如果indexArray為空,那麼末尾項就是唯一的文字項
let lastPrevItemIndex = (indexArray[indexArray.length-1] ? indexArray[indexArray.length-1].pos+indexArray[indexArray.length-1].length : 0);
contentArray.push({type: 0, content: text.substr(lastPrevItemIndex, text.length)});
最後得到的contentArray我們return出去。
比較難的部分在第四步,思路是,我們使用一個指標,對訊息做解析,一開始指標的位置為0
開頭不管如何都push一個文字物件進入contentArray中
直到遇到emoji或者url位置為止
比如遇到的是emoji,我們把emoji解析成物件push到contentArray中,然後指標加上emoji的長度
最後加上末尾。如果末尾不為文字,也新增一個空的文字物件。
完畢。
下面附上所有程式碼
let stringToContentArray = function (text) {
var emojiregex = new RegExp(/ud83c[udf00-udfff]|ud83d[udc00-ude4f]|ud83d[ude80-udeff]/g, `g`);
var matchUrlRegex = new RegExp(/(https?:)//([^/]+)(/[^?]*)?(?[^#]*)?(#.*)?/g,`g`); // 匹配url的正則
var contentArray = [];
if (!text) { // 沒有內容
contentArray.push({ type: 0, "content": `[無內容訊息]` });
return contentArray;
}
var emojiRegArray = text.match(emojiregex); // 匹配了所有的emoji的詞
var urlRegArray = text.match(matchUrlRegex);
// console.log(text);
console.log(`emojiRegArray:`,emojiRegArray);
console.log(`urlRegArray:`,urlRegArray);
if (emojiRegArray === null && urlRegArray === null) { // 沒有emoji表情, 也沒有連結
contentArray.push({ type: 0, "content": text });
return contentArray;
}
var indexEmojiArray = []; // 記錄表情的位置、內容的陣列
var indexUrlArray = []; // 記錄連結的位置、內容的陣列
var indexArray = []; // 以上兩個陣列按照pos順序合併的陣列
var pos1 = -1, pos2 = -1;//頭
if(emojiRegArray){
for (let i = 0; i < emojiRegArray.length; i++) {
pos1 = text.indexOf(emojiRegArray[i], pos1 + 1);
indexEmojiArray.push({
type: 1, // type為1表示是表情
pos: pos1,
length: emojiRegArray[i].length,
res: emojiRegArray[i],
});
}
}
if(urlRegArray){
for (let i = 0; i < urlRegArray.length; i++) {
pos2 = text.indexOf(urlRegArray[i], pos2 + 1);
indexUrlArray.push({
type: 3, // type為1表示是url
pos: pos2,
length: urlRegArray[i].length,
res: urlRegArray[i],
});
}
}
if(emojiRegArray && urlRegArray){
let point1 = 0,point2 = 0;
while(point1 < indexEmojiArray.length || point2 < indexUrlArray.length){
if(!indexEmojiArray[point1]){ // emoji加完了
indexArray.push(indexUrlArray[point2]);
point2++;
}else if(!indexUrlArray[point2]){// url加完了
indexArray.push(indexEmojiArray[point1]);
point1++;
}else{ // 兩個都沒加完
if(indexEmojiArray[point1].pos < indexUrlArray[point2].pos){
indexArray.push(indexEmojiArray[point1]);
point1++;
}else{
indexArray.push(indexUrlArray[point2]);
point2++;
}
}
}
}else if(emojiRegArray && !urlRegArray){ // 有emoji沒有url
indexArray = indexEmojiArray;
}else if(!emojiRegArray && urlRegArray){ // 有url沒有emoji
indexArray = indexUrlArray;
}
console.log("indexArray: ", indexArray);
// 這裡開始把indexArray加工成contentArray
let point = 0; // 記錄當前指標位置
for (let i = 0; i < indexArray.length; i++) {
// 把這一項和上一項之間的內容push成文字
console.log(point);
let textContent = text.substr(point, indexArray[i].pos-point);
console.log(textContent);
contentArray.push({type: 0, content: textContent});
// point += textContent.length;
if(indexArray[i].type === 1){ // 如果這一項是emoji
// contentArray.push({ type: 1, "resources": EMOJI_MAP[indexArray[i].res] || [] });
contentArray.push({ type: 1, resources: indexArray[i].res || [] });
point = indexArray[i].pos + indexArray[i].length;
}else if(indexArray[i].type === 3){ // 如果這一項是url
contentArray.push({ type: 3, url: indexArray[i].res});
point = indexArray[i].pos + indexArray[i].length;
}
}
// 加入末尾項。如果indexArray為空,那麼末尾項就是唯一的文字項
let lastPrevItemIndex = (indexArray[indexArray.length-1] ? indexArray[indexArray.length-1].pos+indexArray[indexArray.length-1].length : 0);
contentArray.push({type: 0, content: text.substr(lastPrevItemIndex, text.length)});
return contentArray;
}