UNPKG

@wulechuan/text-basic-typography

Version:

此为吴乐川《文本简易分割工具与文本简朴排版工具》,不妨称甲、乙。 甲按特定要求将一个文本(“`string`”)分割成片段,形成文本列表(“`string[]`”)。 乙接受一个文本,要求此文本事先含有特别的记号供乙识别, 并据此类记号在原文本之基础上添加空格与换行符(“`\n`”)等内容,产出另一新的文本。 由是,当在纯文字环境(例如命令行形式的会话环境)中呈现该产出之新文本时, 这些文字会因具有简朴的排版而视觉效果更佳。 又,乙调用甲。

419 lines (331 loc) 19.3 kB
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 展平后的单级列表 }