虽然这个问题很旧了,但迄今为止提供的许多解决方案比必要的更为复杂和昂贵,正如user2257198所指出的 - 这完全可以使用短小的一行正则表达式来解决。
然而,我发现他的解决方案存在一些问题,包括:在最大宽度之后而不是之前进行换行,打破明确未包含在字符类中的字符,并且没有考虑现有的换行符导致段落的开头被截断。
这促使我编写了自己的解决方案:
const wrap = (s) => s.replace(
/(?![^\n]{1,32}$)([^\n]{1,32})\s/g, '$1\n'
);
const wrap = (s, w) => s.replace(
new RegExp(`(?![^\\n]{1,${w}}$)([^\\n]{1,${w}})\\s`, 'g'), '$1\n'
);
额外特性
- 处理任何非换行符的字符(例如,代码)。
- 正确处理现有的换行符(例如,段落)。
- 防止在换行符前添加空格。
- 防止在字符串末尾添加不必要的换行符。
解释
主要思路是查找连续的字符序列,这些序列不包含换行符[^\n]
,长度最多为所需长度,例如32 {1,32}
。通过在字符类中使用否定^
,它更加宽容,避免了需要显式添加标点等内容的问题:
str.replace(/([^\n]{1,32})/g, '[$1]\n')
// Matches wrapped in [] to help visualise
"[Lorem ipsum dolor sit amet, cons]
[ectetur adipiscing elit, sed do ]
[eiusmod tempor incididunt ut lab]
[ore et dolore magna aliqua.]
"
到目前为止,这个函数只能确切地在32个字符处进行分割。它之所以有效,是因为它自己的换行插入标记了第一个序列后每个序列的起点。
要按单词进行分割,需要在贪婪量化符号 {1,32}
后加上限定符,以防止其选择在单词中间结束的序列。单词边界字符 \b
可能会导致新行开头的空格,因此必须使用空白字符 \s
。它还必须放置在组外,以便消耗它,以防止增加最大宽度 1 个字符:
str.replace(/([^\n]{1,32})\s/g, '[$1]\n');
// Matches wrapped in [] to help visualise
"[Lorem ipsum dolor sit amet,]
[consectetur adipiscing elit, sed]
[do eiusmod tempor incididunt ut]
[labore et dolore magna]
aliqua."
现在它会在达到限制之前中断单词,但最后一个单词和句号没有在最后的序列中匹配,因为没有终止空格。
可以在空格中添加“或字符串结尾”选项(\s|$)
以扩展匹配,但最好完全防止匹配最后一行,因为这会导致在结尾插入不必要的新行。为了实现这一点,可以在之前添加完全相同序列的负向先行断言,但使用字符串结尾字符而不是空格字符:
str.replace(/(?![^\n]{1,32}$)([^\n]{1,32})\s/g, '[$1]\n');
// Matches wrapped in [] to help visualise
"[Lorem ipsum dolor sit amet,]
[consectetur adipiscing elit, sed]
[do eiusmod tempor incididunt ut]
labore et dolore magna aliqua."
n
个字符后换行吗? - David Thomas