@wulechuan/text-basic-typography
Version:
此为吴乐川《文本简易分割工具与文本简朴排版工具》,不妨称甲、乙。 甲按特定要求将一个文本(“`string`”)分割成片段,形成文本列表(“`string[]`”)。 乙接受一个文本,要求此文本事先含有特别的记号供乙识别, 并据此类记号在原文本之基础上添加空格与换行符(“`\n`”)等内容,产出另一新的文本。 由是,当在纯文字环境(例如命令行形式的会话环境)中呈现该产出之新文本时, 这些文字会因具有简朴的排版而视觉效果更佳。 又,乙调用甲。
419 lines (331 loc) • 19.3 kB
text/typescript
export type 范_将原文本改造为包含简朴排版的新文本_排版配置 = {
/** @default {0} 【半角数字零】。 */
整体缩进级数: number;
/** @default {' '} 四个【半角空格】。 */
整体缩进之文本: string;
/** @default {'- '} 【半角减号】后跟一个【半角空格】。 */
列表引领符: string;
/** @default {' '} 四个【半角空格】。 */
首层缩进之文本: string;
/** @default {' '} 四个【半角空格】。 */
首层缩进之文本_并含整体缩进: string;
/** @default {' - '} */
首层缩进之文本_含列表引领符: string;
/** @default {' - '} */
首层缩进之文本_含列表引领符_并含整体缩进: string;
/** @default {' '} 四个【半角空格】。本工具自身暂时不采用该值,但不妨供外界使用。 */
非首层之单层缩进之文本: string;
/** @default {1} 【半角数字壹】 */
各段落间的额外空白行数: number;
单行最大视觉宽度等效英语字母个数?: number; // TODO 暂未实现。勿用。
}
export type 范_将原文本改造为包含简朴排版的新文本_分割得到的内容结构树 = string[][];
export type 范_将原文本改造为包含简朴排版的新文本_产出 = {
排版好的内容全文: string;
分割好的内容结构树: 范_将原文本改造为包含简朴排版的新文本_分割得到的内容结构树;
最终采纳的排版配置: 范_将原文本改造为包含简朴排版的新文本_排版配置;
}
/**
* 本函数用以在纯文本环境中,对给出的文本进行简朴排版。
* 给出的文本内容须事先插入标记,供本程序识别。
* 标记的使用规则如下:
*
* 1. 【原文本】自身就包含的换行符( “\n” 或 “\r” )。
*
* - 若单独存在(即相邻再无旁他换行符),其会被删去。
* 文本则会在其原来的位置分割,以便另起一【行】。
*
* - 若连续多个换行符彼此紧邻,它们均会被删去。
* 文本则会在其原来的位置分割,以便另起一【段】。
*
* - 若单个换行符之后有一个或多个空格跟随,
* 则该换行符与后续空格会一并删去。
* 文本则会在其原来的位置分割,以便另起一【段】。
*
* - 特别的,“\r\n”二者连用视作【单】个换行符。
* 但除此情形外,“\n”、“\r”二者不可混合连用。
* 即,“\n\r”是不标准的写法,效果难以期料。本人也无意尝试。
*
* 2. 【原文本】中的任何标点符号,若后面 *__没有__* 空格跟随,
* 则该标点符号之后无任何特别的排版处理。
*
* 3. 凡汉语的【句号】、【感叹号】、【问号】、【冒号】和【省略号】:
*
* - 若后面仅 *__唯一一个__* 空格跟随,则该空格会被删去。
* 且上述标点符号之后另起一【行】。
*
* - 若后面有 *__多个__* 空格跟随,则这些空格会被删去。
* 且上述标点符号之后另起一【段】。
*
* 4. 凡汉语的【顿号】、【逗号】和【分号】:
*
* - 若后面有 *__一个或多个__* 空格跟随,则这些空格会被删去。
* 且上述标点符号之后另起一【行】。
* 不妨强调,这些标点之后 *__永无可能__* 另起一【段】
*
* 5. 汉语【 _后_ 引号】较为特殊:
*
* - 若后面 *__没有__* 空格跟随,照例不做任何处理。
*
* - 若后面仅 *__唯一一个__* 空格跟随,仍不做任何处理!
* 这是因为,不少时候为了视觉美观我(们)希望故意
* 在【前引号】之前、【后引号】之后各留一个空格。
* 此时,我(们)的意图 _并不是_ 暗示本工具在此【后引号】之后另起一行。
*
* - 若后面 *__恰好有 2 个__* 空格跟随,则这两个空格会被删去。
* 且该【后引号】之后会另起一【行】。
*
* - 若后面有 *__3 个或更多__* 空格跟随,则这些空格全会被删去。
* 且该【后引号】之后会另起一【段】。
*/
export function 将原文本改造为包含简朴排版的新文本 ({
原文本,
列表引领符,
单层缩进空格数或单层缩进之文本,
整体缩进级数,
各段落间的额外空白行数,
}: {
原文本?: string;
/** @default {'- '} 【半角减号】后跟一个【半角空格】。 */
列表引领符?: string;
/** @default {' '} 四个【半角空格】。 */
单层缩进空格数或单层缩进之文本?: number | string;
/** @default {0} 【半角数字零】。 */
整体缩进级数?: number;
/** @default {1} 【半角数字壹】 */
各段落间的额外空白行数?: number;
} = {}): 范_将原文本改造为包含简朴排版的新文本_产出 {
let 单层缩进之文本: string = ' '.repeat(4)
if (typeof 单层缩进空格数或单层缩进之文本 === 'string') {
if (单层缩进空格数或单层缩进之文本.trim()) {
单层缩进之文本 = 单层缩进空格数或单层缩进之文本
} else {
const 等效的数字 = +单层缩进空格数或单层缩进之文本
if (0 < 等效的数字 && 等效的数字 < 128) {
单层缩进之文本 = ' '.repeat(等效的数字)
} else {
单层缩进之文本 = 单层缩进空格数或单层缩进之文本
}
}
} else if (typeof 单层缩进空格数或单层缩进之文本 === 'number') {
if (0 < 单层缩进空格数或单层缩进之文本 && 单层缩进空格数或单层缩进之文本 < 128) {
单层缩进之文本 = ' '.repeat(单层缩进空格数或单层缩进之文本)
}
}
if (typeof 列表引领符 !== 'string') {
列表引领符 = '- '
}
整体缩进级数 = +(整体缩进级数 || 0)
if (!(整体缩进级数 > 0)) {
整体缩进级数 = 0
}
各段落间的额外空白行数 = +(各段落间的额外空白行数 || -1)
if (!(各段落间的额外空白行数 >= 0)) {
各段落间的额外空白行数 = 1
}
const 整体缩进之文本 = 单层缩进之文本.repeat(整体缩进级数)
let 首层缩进之文本 = ''
let 首层缩进之文本_含列表引领符 = ''
if (列表引领符.length > 0) {
首层缩进之文本 = 单层缩进之文本.repeat(Math.ceil((列表引领符.length + 1) / 单层缩进之文本.length))
首层缩进之文本_含列表引领符 = `${首层缩进之文本.slice(0, -列表引领符.length)}${列表引领符}`
}
const 非首层之单层缩进之文本 = 单层缩进之文本 // 在本函数内部无用,但可供外界采用。
const 首层缩进之文本_并含整体缩进 = `${整体缩进之文本}${首层缩进之文本}`
const 首层缩进之文本_含列表引领符_并含整体缩进 = `${整体缩进之文本}${首层缩进之文本_含列表引领符}`
let 各段落文本之列表: any[] = [ 原文本 ]
;[
{ 分割符或分割正则表达式: /。 {2,}/, 在分割符处须补回的内容: '。' },
{ 分割符或分割正则表达式: /! {2,}/, 在分割符处须补回的内容: '!' },
{ 分割符或分割正则表达式: /? {2,}/, 在分割符处须补回的内容: '?' },
{ 分割符或分割正则表达式: /” {3,}/, 在分割符处须补回的内容: '”' },
{ 分割符或分割正则表达式: /: {2,}/, 在分割符处须补回的内容: ':' },
{ 分割符或分割正则表达式: /…… {2,}/, 在分割符处须补回的内容: '……' },
{ 分割符或分割正则表达式: /\n{2,}|\n +/, 在分割符处须补回的内容: '' },
{ 分割符或分割正则表达式: /\r{2,}|\r +/, 在分割符处须补回的内容: '' },
{ 分割符或分割正则表达式: /(\r\n){2,}|\r\n +/, 在分割符处须补回的内容: '' },
].forEach(分割方案 => {
各段落文本之列表 = 对文本列表逐项分割并将结果列表展平({
原文本或原文本列表: 各段落文本之列表,
...分割方案,
})
})
const 各段落文本列表之列表: string[][] = 各段落文本之列表.map(某段落之文本 => {
let 某段落中各行文本之列表 = [ 某段落之文本 ]
;[
{ 分割符或分割正则表达式: /。 /, 在分割符处须补回的内容: '。' },
{ 分割符或分割正则表达式: /! /, 在分割符处须补回的内容: '!' },
{ 分割符或分割正则表达式: /? /, 在分割符处须补回的内容: '?' },
{ 分割符或分割正则表达式: /” {2}/, 在分割符处须补回的内容: '”' },
{ 分割符或分割正则表达式: /: /, 在分割符处须补回的内容: ':' },
{ 分割符或分割正则表达式: /…… /, 在分割符处须补回的内容: '……' },
{ 分割符或分割正则表达式: /、 +/, 在分割符处须补回的内容: '、' },
{ 分割符或分割正则表达式: /, +/, 在分割符处须补回的内容: ',' },
{ 分割符或分割正则表达式: /; +/, 在分割符处须补回的内容: ';' },
{ 分割符或分割正则表达式: /\n/, 在分割符处须补回的内容: '' },
{ 分割符或分割正则表达式: /\r/, 在分割符处须补回的内容: '' },
{ 分割符或分割正则表达式: /\r\n/, 在分割符处须补回的内容: '' },
].forEach(分割方案 => {
某段落中各行文本之列表 = 对文本列表逐项分割并将结果列表展平({
原文本或原文本列表: 某段落中各行文本之列表,
...分割方案,
})
})
return 某段落中各行文本之列表
})
const 排版好的内容全文 = 各段落文本列表之列表.map((某段落中各行文本之列表) => {
return 某段落中各行文本之列表.map((某行之文本, 行之内部编号) => {
const 行号 = 行之内部编号 + 1
const 行前缀内容 = `${整体缩进之文本}${行号 === 1 ? 首层缩进之文本_含列表引领符 : 首层缩进之文本}`
return `${行前缀内容}${某行之文本}`
}).join('\n')
}).join('\n'.repeat(1 + 各段落间的额外空白行数))
const 产出: 范_将原文本改造为包含简朴排版的新文本_产出 = {
排版好的内容全文,
分割好的内容结构树: 各段落文本列表之列表,
最终采纳的排版配置: {
整体缩进级数,
整体缩进之文本,
列表引领符,
首层缩进之文本,
首层缩进之文本_并含整体缩进,
首层缩进之文本_含列表引领符,
首层缩进之文本_含列表引领符_并含整体缩进,
非首层之单层缩进之文本,
各段落间的额外空白行数,
},
}
return 产出
}
export type 范_对文本列表逐项分割并将结果列表展平 = {
(配置项集: {
原文本或原文本列表: string[];
分割符或分割正则表达式?: string | RegExp;
在分割符处须补回的内容?: string;
}): string[];
(配置项集: {
原文本或原文本列表: string;
分割符或分割正则表达式?: string | RegExp;
在分割符处须补回的内容?: string;
}): string;
(配置项集?: {
原文本或原文本列表?: any;
分割符或分割正则表达式?: string | RegExp;
在分割符处须补回的内容?: string;
}): any;
}
/**
* 本函数接受原文信息,并据此有所产出。原文可以是文本列表(“`string[]`”),也可以是单一文本(“`string`”)。
*
* - 如果原文是文本列表(“`string[]`”),本程序会
*
* 1. 将其中的所有文本逐一分割,
* 故从每一个原始文本各得到一个文本片段列表(“`string[]`”)。
*
* 2. 将这些文本片段列表汇总在一个列表中,
* 临时得到一个双层嵌套列表,即 (“`string[][]`”)。
*
* - 如果给出的原文是一个文本“`string`”)而非文本列表(“`string[]`”),
* 则本函数会先创建一个列表,将该文本包裹其中,而后照常处理该列表。
*
| - - - - - - - - - - - - - - - - - - - - -
*
* 本函数接受唯一的参数,不妨称 `配置项集` 。配置项集中可以给出以下 3 个属性。
*
| - - - - - - - - - - - - - - - - - - - - -
*
* 1. `配置项集.原文本或原文本列表`:
*
* 顾名思义,该值可为文本或文本列表。若为文本,则本函数会先创建一个列表,将该文本包裹其中,而后照常处理该列表。
*
* 2. `配置项集.分割符或分割正则表达式`:
*
* 顾名思义,可为文本,亦可为正则表达式实例。用以分隔原文本列表中的每一项文本。
*
* 3. `配置项集.在分割符处须补回的内容`:
*
* 我们知道, JavaScript 和 TypeScript 中的字符串分割函数(“`split`”)
* 无法在分割得到的结果列表中包含分割符本身。即,分割符会消失在结果中。
* 为确保原文本在经历排版设计后内容仍完整无缺,
* 需在分割后将那些用作分割符的字符补回。
* 此项即为须补回的字符。
*
* 顺便指出,该字符可以任意设计,故实际上可以实现对用作分割符的文本进行“偷换”。
*
* 还需注意,
*
* - 如果 `配置项集.分割符或分割正则表达式` 是文本,
* 则 `配置项集.在分割符处须补回的内容` 可以省略。
* 若省略,则其取值会照搬前者(即 `配置项集.分割符或分割正则表达式` )。
* 丢失什么就补回什么,这也合乎常情。
*
* - 如果 `配置项集.分割符或分割正则表达式` 是正则表达式,
* 则 `配置项集.在分割符处须补回的内容` 虽然可以省略,但本程序会给出警告。
* 因为,从正则表达式的 “`.toString`” 得到的文本视作“补回的”文本往往是不合适的!
*
* - 另需指出,补回的文本配置为“空字符串”是允许的,不会视为省略。
*/
export const 对文本列表逐项分割并将结果列表展平: 范_对文本列表逐项分割并将结果列表展平 = 配置项集 => {
if (!配置项集 || typeof 配置项集 !== 'object') { 配置项集 = {} }
const {
原文本或原文本列表: 原文本或原文本列表_给出值,
分割符或分割正则表达式: 分割符或分割正则表达式_给出值,
在分割符处须补回的内容: 在分割符处须补回的内容_给出值,
} = 配置项集
let 原文本列表_采纳值1: any[]
if (typeof 原文本或原文本列表_给出值 === 'string') {
原文本列表_采纳值1 = [ 原文本或原文本列表_给出值 ]
} else if (Array.isArray(原文本或原文本列表_给出值)) {
原文本列表_采纳值1 = 原文本或原文本列表_给出值
} else {
原文本列表_采纳值1 = []
}
const 原文本列表_采纳值2: string[] = 原文本列表_采纳值1.filter(
某项内容 => typeof 某项内容 === 'string' && !!某项内容
)
if (原文本列表_采纳值2.length < 1) {
// console.debug('给出的原文内容无效,或内容全空。')
return 原文本或原文本列表_给出值
}
const 在分割符处须补回内容未给出 = typeof 在分割符处须补回的内容_给出值 !== 'string'
let 分割符或分割正则表达式_采纳值: string | RegExp
let 分割符等效的文本 = ''
let 在分割符处须补回的内容_采纳值: string
let 分割符为正则表达式时补回内容却未给出 = false
if (typeof 分割符或分割正则表达式_给出值 === 'string') {
分割符或分割正则表达式_采纳值 = 分割符或分割正则表达式_给出值
分割符等效的文本 = 分割符或分割正则表达式_给出值
} else {
if (分割符或分割正则表达式_给出值 instanceof RegExp) {
分割符或分割正则表达式_采纳值 = 分割符或分割正则表达式_给出值
分割符等效的文本 = 分割符或分割正则表达式_给出值.toString()
分割符为正则表达式时补回内容却未给出 = 在分割符处须补回内容未给出
} else {
分割符或分割正则表达式_采纳值 = ''
}
}
if (!在分割符处须补回内容未给出) {
在分割符处须补回的内容_采纳值 = 在分割符处须补回的内容_给出值
} else {
在分割符处须补回的内容_采纳值 = 分割符等效的文本
if (分割符为正则表达式时补回内容却未给出) {
console.warn(`给出的【分割符】系正则表达式,但【分割符处须补回的内容】并未明确给出。故补回的内容是不可靠的!据目前给出的配置,拟在各分割处补回该内容: "${在分割符处须补回的内容_采纳值}"。`)
}
}
if (!分割符或分割正则表达式_采纳值) {
console.debug('【分割符】为空字符串。')
}
const 两级嵌套之列表 = 原文本列表_采纳值2.map(原文某项文本 => {
return 原文某项文本.split(分割符或分割正则表达式_采纳值).map((分割后的某文本片段, 片段之内部编号, 分割得到的片段列表) => {
const 不是最末的片段 = 片段之内部编号 < 分割得到的片段列表.length - 1
return 不是最末的片段 ? `${分割后的某文本片段}${在分割符处须补回的内容_采纳值}` : 分割后的某文本片段
})
})
const 展平后的单级列表 = 两级嵌套之列表.reduce((展平后的文本片段列表, 某原文本分割而得的片段列表) => {
return [
...展平后的文本片段列表,
...某原文本分割而得的片段列表,
]
}, [])
return 展平后的单级列表
}