@@ -114,7 +114,7 @@ export class Cvb
114114 let arrFileMatch : RegExpExecArray | null ;
115115 while ( ( arrFileMatch = regFile . exec ( strCvbContentPart ) ) !== null )
116116 {
117- const strFilePath : string = arrFileMatch [ 1 ] ;
117+ const strFilePath : string = filePathNormalize ( arrFileMatch [ 1 ] ) ;
118118 let strFileContent : string = arrFileMatch [ 2 ] . trim ( ) ;
119119 // 去除代码块标记
120120 const regCodeBlock : RegExp = / ^ ` ` ` .* \n ( [ \s \S ] * ?) \n ` ` ` $ / m;
@@ -152,14 +152,14 @@ abstract class TcvbOperation
152152{
153153 constructor (
154154 public readonly m_strFilePath : string ,
155- public readonly m_strType : 'single -replace' | 'global-replace' | 'insert' | 'delete ' | 'create'
155+ public readonly m_strType : 'exact -replace' | 'global-replace' | 'create'
156156 )
157157 {
158158 }
159159}
160160
161- // 1. 单个替换操作(SINGLE -REPLACE)
162- class SingleReplaceOperation extends TcvbOperation
161+ // 1. 单个替换操作(EXACT -REPLACE)
162+ class ExactReplaceOperation extends TcvbOperation
163163{
164164 public m_strBeforeAnchor : string ;
165165 public m_strAfterAnchor : string ;
@@ -174,7 +174,7 @@ class SingleReplaceOperation extends TcvbOperation
174174 m_strNewContent : string
175175 )
176176 {
177- super ( m_strFilePath , 'single -replace' ) ;
177+ super ( m_strFilePath , 'exact -replace' ) ;
178178 this . m_strBeforeAnchor = m_strBeforeAnchor ;
179179 this . m_strAfterAnchor = m_strAfterAnchor ;
180180 this . m_strOldContent = m_strOldContent ;
@@ -200,49 +200,7 @@ class GlobalReplaceOperation extends TcvbOperation
200200 }
201201}
202202
203- // 3. 插入操作(INSERT)
204- class InsertOperation extends TcvbOperation
205- {
206- public m_strBeforeAnchor : string ;
207- public m_strAfterAnchor : string ;
208- public m_strInsertContent : string ;
209-
210- constructor (
211- m_strFilePath : string ,
212- m_strBeforeAnchor : string ,
213- m_strAfterAnchor : string ,
214- m_strInsertContent : string
215- )
216- {
217- super ( m_strFilePath , 'insert' ) ;
218- this . m_strBeforeAnchor = m_strBeforeAnchor ;
219- this . m_strAfterAnchor = m_strAfterAnchor ;
220- this . m_strInsertContent = m_strInsertContent ;
221- }
222- }
223-
224- // 4. 删除操作(DELETE)
225- class DeleteOperation extends TcvbOperation
226- {
227- public m_strBeforeAnchor : string ;
228- public m_strAfterAnchor : string ;
229- public m_strDeleteContent : string ;
230-
231- constructor (
232- m_strFilePath : string ,
233- m_strBeforeAnchor : string ,
234- m_strAfterAnchor : string ,
235- m_strDeleteContent : string
236- )
237- {
238- super ( m_strFilePath , 'delete' ) ;
239- this . m_strBeforeAnchor = m_strBeforeAnchor ;
240- this . m_strAfterAnchor = m_strAfterAnchor ;
241- this . m_strDeleteContent = m_strDeleteContent ;
242- }
243- }
244-
245- // 5. 创建操作(CREATE)——新写文件,后面直接跟正文内容即可
203+ // 3. 创建操作(CREATE)——新写文件,后面直接跟正文内容即可
246204class CreateOperation extends TcvbOperation
247205{
248206 public m_strContent : string ;
@@ -275,7 +233,7 @@ export class TCVB
275233 {
276234 const strFilePath : string = filePathNormalize ( arrFileMatch [ 1 ] ) ;
277235 const strOperationsBlock : string = arrFileMatch [ 2 ] ;
278- // 支持操作类型中含有 "-" 符号(如 single -replace 等)
236+ // 支持操作类型中含有 "-" 符号(如 exact -replace 等)
279237 const regOperation : RegExp = / ^ # # O P E R A T I O N : ( [ \w - ] + ) \n ( [ \s \S ] * ?) (? = ^ # # O P E R A T I O N : | (? ! [ \s \S ] ) ) / gm;
280238 let arrOpMatch : RegExpExecArray | null ;
281239 while ( ( arrOpMatch = regOperation . exec ( strOperationsBlock ) ) !== null )
@@ -293,18 +251,12 @@ export class TCVB
293251 {
294252 switch ( strType )
295253 {
296- case 'single-replace' :
297- this . parseSingleReplace ( strFilePath , strContent ) ;
298- break ;
299254 case 'global-replace' :
300255 this . parseGlobalReplace ( strFilePath , strContent ) ;
301256 break ;
302- case 'insert' :
303- this . parseInsert ( strFilePath , strContent ) ;
304- break ;
305- case 'delete' :
306- this . parseDelete ( strFilePath , strContent ) ;
307- break ;
257+ case 'exact-replace' :
258+ this . parseExactReplace ( strFilePath , strContent ) ;
259+ break ;
308260 case 'create' :
309261 this . parseCreate ( strFilePath , strContent ) ;
310262 break ;
@@ -318,11 +270,11 @@ export class TCVB
318270 }
319271 }
320272
321- // SINGLE -REPLACE 操作解析:要求 BEFORE_ANCHOR、AFTER_ANCHOR、OLD_CONTENT、NEW_CONTENT 四个段落
322- private parseSingleReplace ( strFilePath : string , strContent : string ) : void
273+ // Exact -REPLACE 操作解析:要求 BEFORE_ANCHOR、AFTER_ANCHOR、OLD_CONTENT、NEW_CONTENT 四个段落
274+ private parseExactReplace ( strFilePath : string , strContent : string ) : void
323275 {
324276 const recSections = this . parseSections ( strContent , [ 'BEFORE_ANCHOR' , 'AFTER_ANCHOR' , 'OLD_CONTENT' , 'NEW_CONTENT' ] ) ;
325- this . m_arrOperations . push ( new SingleReplaceOperation (
277+ this . m_arrOperations . push ( new ExactReplaceOperation (
326278 strFilePath ,
327279 recSections [ 'BEFORE_ANCHOR' ] ,
328280 recSections [ 'AFTER_ANCHOR' ] ,
@@ -342,30 +294,6 @@ export class TCVB
342294 ) ) ;
343295 }
344296
345- // INSERT 操作解析:要求 BEFORE_ANCHOR、AFTER_ANCHOR、INSERT_CONTENT 三个段落
346- private parseInsert ( strFilePath : string , strContent : string ) : void
347- {
348- const recSections = this . parseSections ( strContent , [ 'BEFORE_ANCHOR' , 'AFTER_ANCHOR' , 'INSERT_CONTENT' ] ) ;
349- this . m_arrOperations . push ( new InsertOperation (
350- strFilePath ,
351- recSections [ 'BEFORE_ANCHOR' ] ,
352- recSections [ 'AFTER_ANCHOR' ] ,
353- recSections [ 'INSERT_CONTENT' ]
354- ) ) ;
355- }
356-
357- // DELETE 操作解析:要求 BEFORE_ANCHOR、AFTER_ANCHOR、DELETE_CONTENT 三个段落
358- private parseDelete ( strFilePath : string , strContent : string ) : void
359- {
360- const recSections = this . parseSections ( strContent , [ 'BEFORE_ANCHOR' , 'AFTER_ANCHOR' , 'DELETE_CONTENT' ] ) ;
361- this . m_arrOperations . push ( new DeleteOperation (
362- strFilePath ,
363- recSections [ 'BEFORE_ANCHOR' ] ,
364- recSections [ 'AFTER_ANCHOR' ] ,
365- recSections [ 'DELETE_CONTENT' ]
366- ) ) ;
367- }
368-
369297 // CREATE 操作解析:直接将正文内容作为新文件内容,可选地去除 Markdown 代码块
370298 private parseCreate ( strFilePath : string , strContent : string ) : void
371299 {
@@ -481,44 +409,26 @@ TCVB 格式规范:
481409...
482410
483411操作类型:
484- 1. 单个替换操作(SINGLE-REPLACE):
485- ## OPERATION:SINGLE-REPLACE
486- ## BEFORE_ANCHOR
487- [markdown代码块:被替换行之前的锚点内容,用来划定范围,避免混淆, 注意这个不需要紧挨着被替换行]
488- ## AFTER_ANCHOR
489- [markdown代码块:被替换行之后的锚点内容,用来划定范围,避免混淆, 注意这个不需要紧挨着被替换行]
490- ## OLD_CONTENT
491- [markdown代码块:被替换内容]
492- ## NEW_CONTENT
493- [markdown代码块:新内容]
494412
495- 2 . 全局替换操作( GLOBAL-REPLACE) :
413+ 1 . 全局替换操作( GLOBAL-REPLACE) :
496414## OPERATION:GLOBAL-REPLACE
497415## OLD_CONTENT
498416[markdown代码块:被全局替换的内容]
499417## NEW_CONTENT
500418[markdown代码块:新内容]
501419
502- 3. 插入操作(INSERT):
503- ## OPERATION:INSERT
504- ## BEFORE_ANCHOR
505- [markdown码块:插入位置前的锚点内容,要紧挨着插入的行位置]
506- ## AFTER_ANCHOR
507- [markdown代码块:插入位置后的锚点内容, 要紧挨着插入的行位置,注意不要与插入的新内容相同,是原始内容插入位置之后的内容]
508- ## INSERT_CONTENT
509- [markdown代码块:插入的新内容]
510-
511- 4. 删除操作(DELETE):
512- ## OPERATION:DELETE
420+ 2. 精确替换操作(EXACT-REPLACE),用于替换全局替换无法精准定位的情况:
421+ ## OPERATION:EXACT-REPLACE
422+ ## OLD_CONTENT
423+ [markdown代码块:被替换内容]
424+ ## NEW_CONTENT
425+ [markdown代码块:新内容]
513426## BEFORE_ANCHOR
514- [markdown代码块:被删除行前的锚点内容 ]
427+ [markdown代码块:OLD_CONTENT之前的几行内容, 用来划定范围上半段锚点,避免有多个类似匹配, 注意这个不需要紧挨着被替换行。 不能和OLD_CONTENT重合 ]
515428## AFTER_ANCHOR
516- [markdown代码块:被删除行后的锚点内容,注意不要与要删除的内容相同,是原始内容被删除代码块之后的内容]
517- ## DELETE_CONTENT
518- [markdown代码块:被删除行的内容]
429+ [markdown代码块:OLD_CONTENT之前的几行内容, 用来划定范围下半段锚点,避免有多个类似匹配, 注意这个不需要紧挨着被替换行。 不能和OLD_CONTENT重合]
519430
520-
521- 5. 创建操作(CREATE):
431+ 3. 创建操作(CREATE):
522432## OPERATION:CREATE
523433[markdown代码块:直接跟正文内容,表示新文件的全部内容]
524434
@@ -528,6 +438,8 @@ TCVB 格式规范:
5284383. 锚点为连续的多行内容:使用至少3行唯一文本作为锚点,用来标定范围,防止混淆(如果需要可以超过3行)
5294394. [markdown代码块], 一定要用\`\`\` ... \`\`\` 包裹,仔细检查不要漏掉
5304405. 注意TCVB和CVB的区别。CVB是完整的内容,而TCVB是用来生成差量同步的,通过多个OPERATION去操作已有CVB合成新CVB
441+ 6. 插入和删除操作都可以转化为替换操作
442+ 7. 用来匹配的锚点必须和原始传入的数据一致,不能有缺失,比如不能丢弃注释。
531443` ;
532444 }
533445}
@@ -557,22 +469,14 @@ export function mergeCvb(baseCvb: Cvb, tcvb: TCVB) : Cvb
557469 let strContent : string = mapMergedFiles . get ( strFilePath ) || '' ;
558470 for ( const op of arrOperations )
559471 {
560- if ( op instanceof SingleReplaceOperation )
472+ if ( op instanceof ExactReplaceOperation )
561473 {
562- strContent = applySingleReplace ( strContent , op ) ;
474+ strContent = applyExactReplace ( strContent , op ) ;
563475 }
564476 else if ( op instanceof GlobalReplaceOperation )
565477 {
566478 strContent = applyGlobalReplace ( strContent , op ) ;
567479 }
568- else if ( op instanceof InsertOperation )
569- {
570- strContent = applyInsert ( strContent , op ) ;
571- }
572- else if ( op instanceof DeleteOperation )
573- {
574- strContent = applyDelete ( strContent , op ) ;
575- }
576480 else if ( op instanceof CreateOperation )
577481 {
578482 // CREATE 操作:直接以新内容覆盖原有内容
@@ -589,15 +493,16 @@ export function mergeCvb(baseCvb: Cvb, tcvb: TCVB) : Cvb
589493 return rebuildCvb ( baseCvb , mapMergedFiles ) ;
590494}
591495
592- function applySingleReplace ( strContent : string , op : SingleReplaceOperation ) : string {
496+ function applyExactReplace ( strContent : string , op : ExactReplaceOperation ) : string {
593497 // 构建匹配模式:前锚点 + 中间内容1 + 旧内容 + 中间内容2 + 后锚点
594498 const regPattern = buildPattern ( op . m_strBeforeAnchor , op . m_strOldContent , op . m_strAfterAnchor ) ;
595499 // 替换为:前锚点 + 中间内容1 + 新内容 + 中间内容2 + 后锚点
596500 const strReplacement = op . m_strBeforeAnchor + '$1' + op . m_strNewContent + '$2' + op . m_strAfterAnchor ;
597501
598502 regPattern . lastIndex = 0 ; // 重置正则表达式的状态
599503 if ( ! regPattern . test ( strContent ) ) {
600- throw new Error ( `Single-replace操作失败:文件 "${ op . m_strFilePath } " 中未找到匹配项。请检查前后锚点及旧内容是否正确。` ) ;
504+ console . log ( "以下表达式:\n" + regPattern + "\n 为何无法匹配到原文:\n" + strContent ) ;
505+ throw new Error ( `Exact-replace操作失败:文件 "${ op . m_strFilePath } " 中未找到匹配项。请检查前后锚点及旧内容是否正确。` ) ;
601506 }
602507 regPattern . lastIndex = 0 ; // 再次重置以备替换
603508
@@ -615,63 +520,14 @@ function applyGlobalReplace(strContent: string, op: GlobalReplaceOperation) : st
615520
616521 regPattern . lastIndex = 0 ;
617522 if ( ! regPattern . test ( strContent ) ) {
523+ console . log ( "以下表达式:\n" + regPattern + "\n 为何无法匹配到原文:\n" + strContent ) ;
618524 throw new Error ( `全局替换失败:文件 "${ op . m_strFilePath } " 中未找到旧内容 "${ op . m_strOldContent } "。` ) ;
619525 }
620526 regPattern . lastIndex = 0 ;
621527
622528 return strContent . replace ( regPattern , op . m_strNewContent ) ;
623529}
624530
625- function applyInsert ( strContent : string , op : InsertOperation ) : string
626- {
627- if ( op . m_strBeforeAnchor === "" || op . m_strAfterAnchor === "" )
628- {
629- throw new Error ( "插入操作的前锚点或后锚点为空" ) ;
630- }
631-
632- // 将前后锚点转义后使用
633- const strBeforeRegex : string = normalizeLineWhitespace ( escapeRegExp ( op . m_strBeforeAnchor ) ) ;
634- const strAfterRegex : string = normalizeLineWhitespace ( escapeRegExp ( op . m_strAfterAnchor ) ) ;
635- // 中间部分直接作为正则片段,不做转义
636- const strMiddlePattern : string = "[ \\t\\r\\n]*" ;
637-
638- const regPattern : RegExp = new RegExp ( strBeforeRegex + strMiddlePattern + strAfterRegex , 'gs' ) ;
639- const strReplacement : string = op . m_strBeforeAnchor + op . m_strInsertContent + op . m_strAfterAnchor ;
640-
641- console . log ( regPattern ) ;
642- regPattern . lastIndex = 0 ;
643- if ( ! regPattern . test ( strContent ) ) {
644- throw new Error ( `插入失败:文件 "${ op . m_strFilePath } " 插入前锚点:"${ op . m_strBeforeAnchor } ", 插入内容: "${ op . m_strInsertContent } ", 插入后锚点: "${ op . m_strAfterAnchor } "` ) ;
645- }
646- regPattern . lastIndex = 0 ;
647-
648- return strContent . replace ( regPattern , strReplacement ) ;
649- }
650-
651- function applyDelete ( strContent : string , op : DeleteOperation ) : string
652- {
653- if ( op . m_strBeforeAnchor === "" || op . m_strAfterAnchor === "" )
654- {
655- throw new Error ( "删除操作的前锚点或后锚点为空" ) ;
656- }
657- if ( op . m_strDeleteContent === "" )
658- {
659- throw new Error ( "删除操作的内容为空" ) ;
660- }
661-
662- const regPattern : RegExp = buildPattern ( op . m_strBeforeAnchor , op . m_strDeleteContent , op . m_strAfterAnchor ) ;
663- // 使用捕获组 $1 和 $2 来保留匹配到的前后附加内容(不删除)
664- const strReplacement : string = op . m_strBeforeAnchor + "$1" + "$2" + op . m_strAfterAnchor ;
665-
666- regPattern . lastIndex = 0 ;
667- if ( ! regPattern . test ( strContent ) ) {
668- throw new Error ( `删除失败:文件 "${ op . m_strFilePath } " 需要删除的内容:"${ op . m_strDeleteContent } "` ) ;
669- }
670- regPattern . lastIndex = 0 ;
671-
672- return strContent . replace ( regPattern , strReplacement ) ;
673- }
674-
675531// 根据前锚点、内容、后锚点构建正则表达式(dotall 模式)
676532function buildPattern ( strBefore : string , strContent : string , strAfter : string ) : RegExp {
677533 return new RegExp (
@@ -727,7 +583,7 @@ function normalizeLineWhitespace(anchor: string): string {
727583
728584function filePathNormalize ( strRawPath : string ) : string
729585{
730- return path . normalize ( strRawPath . replace ( / ^ [ \\ / ] + / , '' ) ) ;
586+ return path . normalize ( strRawPath . replace ( / ^ [ \\ / ] + / , '' ) . trim ( ) ) ;
731587}
732588
733589/**
0 commit comments