@@ -475,7 +475,7 @@ TCVB 格式规范:
475475## END_TCVB
476476
477477文件块格式:
478- ## FILE:<文件路径 >
478+ ## FILE:<文件绝对路径 >
479479[操作1]
480480[操作2]
481481...
@@ -484,48 +484,49 @@ TCVB 格式规范:
4844841. 单个替换操作(SINGLE-REPLACE):
485485## OPERATION:SINGLE-REPLACE
486486## BEFORE_ANCHOR
487- [代码块 :被替换行之前的锚点内容,用来划定范围,避免混淆, 注意这个不需要紧挨着被替换行]
487+ [markdown代码块 :被替换行之前的锚点内容,用来划定范围,避免混淆, 注意这个不需要紧挨着被替换行]
488488## AFTER_ANCHOR
489- [代码块 :被替换行之后的锚点内容,用来划定范围,避免混淆, 注意这个不需要紧挨着被替换行]
489+ [markdown代码块 :被替换行之后的锚点内容,用来划定范围,避免混淆, 注意这个不需要紧挨着被替换行]
490490## OLD_CONTENT
491- [代码块 :被替换内容]
491+ [markdown代码块 :被替换内容]
492492## NEW_CONTENT
493- [代码块 :新内容]
493+ [markdown代码块 :新内容]
494494
4954952. 全局替换操作(GLOBAL-REPLACE):
496496## OPERATION:GLOBAL-REPLACE
497497## OLD_CONTENT
498- [代码块 :被全局替换的内容]
498+ [markdown代码块 :被全局替换的内容]
499499## NEW_CONTENT
500- [代码块 :新内容]
500+ [markdown代码块 :新内容]
501501
5025023. 插入操作(INSERT):
503503## OPERATION:INSERT
504504## BEFORE_ANCHOR
505- [代码块 :插入位置前的锚点内容]
505+ [markdown码块 :插入位置前的锚点内容,要紧挨着插入的行位置 ]
506506## AFTER_ANCHOR
507- [代码块 :插入位置后的锚点内容]
507+ [markdown代码块 :插入位置后的锚点内容, 要紧挨着插入的行位置,注意不要与插入的新内容相同,是原始内容插入位置之后的内容 ]
508508## INSERT_CONTENT
509- [代码块:插入内容 ]
509+ [markdown代码块:插入的新内容 ]
510510
5115114. 删除操作(DELETE):
512512## OPERATION:DELETE
513513## BEFORE_ANCHOR
514- [代码块 :被删除行前的锚点内容]
514+ [markdown代码块 :被删除行前的锚点内容]
515515## AFTER_ANCHOR
516- [代码块 :被删除行后的锚点内容]
516+ [markdown代码块 :被删除行后的锚点内容,注意不要与要删除的内容相同,是原始内容被删除代码块之后的内容 ]
517517## DELETE_CONTENT
518- [代码块:被删除行的内容]
518+ [markdown代码块:被删除行的内容]
519+
519520
5205215. 创建操作(CREATE):
521522## OPERATION:CREATE
522- [代码块 :直接跟正文内容,表示新文件的全部内容]
523+ [markdown代码块 :直接跟正文内容,表示新文件的全部内容]
523524
524525注意:
5255261. 所有OPERATION操作以行为单位
5265272. 一个'## FILE'下可以有多个'## OPERATION'
5275283. 锚点为连续的多行内容:使用至少3行唯一文本作为锚点,用来标定范围,防止混淆(如果需要可以超过3行)
528- 4. 代码块,(规则中的[代码块: ...]) 用 markdown 格式包裹
529+ 4. [markdown代码块], 一定要用\`\`\` ... \`\`\` 包裹,仔细检查不要漏掉
5295305. 注意TCVB和CVB的区别。CVB是完整的内容,而TCVB是用来生成差量同步的,通过多个OPERATION去操作已有CVB合成新CVB
530531` ;
531532 }
@@ -549,35 +550,40 @@ export function mergeCvb(baseCvb: Cvb, tcvb: TCVB) : Cvb
549550 mapOperationsByFile . get ( op . m_strFilePath ) ! . push ( op ) ;
550551 }
551552
552- // 对每个文件执行所有操作(按顺序执行)
553- for ( const [ strFilePath , arrOperations ] of mapOperationsByFile )
554- {
555- let strContent : string = mapMergedFiles . get ( strFilePath ) || '' ;
556- for ( const op of arrOperations )
557- {
558- if ( op instanceof SingleReplaceOperation )
559- {
560- strContent = applySingleReplace ( strContent , op ) ;
561- }
562- else if ( op instanceof GlobalReplaceOperation )
563- {
564- strContent = applyGlobalReplace ( strContent , op ) ;
565- }
566- else if ( op instanceof InsertOperation )
553+ try {
554+ // 对每个文件执行所有操作(按顺序执行)
555+ for ( const [ strFilePath , arrOperations ] of mapOperationsByFile )
567556 {
568- strContent = applyInsert ( strContent , op ) ;
569- }
570- else if ( op instanceof DeleteOperation )
571- {
572- strContent = applyDelete ( strContent , op ) ;
573- }
574- else if ( op instanceof CreateOperation )
575- {
576- // CREATE 操作:直接以新内容覆盖原有内容
577- strContent = op . m_strContent ;
557+ let strContent : string = mapMergedFiles . get ( strFilePath ) || '' ;
558+ for ( const op of arrOperations )
559+ {
560+ if ( op instanceof SingleReplaceOperation )
561+ {
562+ strContent = applySingleReplace ( strContent , op ) ;
563+ }
564+ else if ( op instanceof GlobalReplaceOperation )
565+ {
566+ strContent = applyGlobalReplace ( strContent , op ) ;
567+ }
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+ }
576+ else if ( op instanceof CreateOperation )
577+ {
578+ // CREATE 操作:直接以新内容覆盖原有内容
579+ strContent = op . m_strContent ;
580+ }
581+ }
582+ mapMergedFiles . set ( strFilePath , strContent ) ;
578583 }
579- }
580- mapMergedFiles . set ( strFilePath , strContent ) ;
584+ }
585+ catch ( err : any ) {
586+ throw new Error ( `合并CVB失败: ${ err . message } ` ) ;
581587 }
582588
583589 return rebuildCvb ( baseCvb , mapMergedFiles ) ;
@@ -588,6 +594,13 @@ function applySingleReplace(strContent: string, op: SingleReplaceOperation): str
588594 const regPattern = buildPattern ( op . m_strBeforeAnchor , op . m_strOldContent , op . m_strAfterAnchor ) ;
589595 // 替换为:前锚点 + 中间内容1 + 新内容 + 中间内容2 + 后锚点
590596 const strReplacement = op . m_strBeforeAnchor + '$1' + op . m_strNewContent + '$2' + op . m_strAfterAnchor ;
597+
598+ regPattern . lastIndex = 0 ; // 重置正则表达式的状态
599+ if ( ! regPattern . test ( strContent ) ) {
600+ throw new Error ( `Single-replace操作失败:文件 "${ op . m_strFilePath } " 中未找到匹配项。请检查前后锚点及旧内容是否正确。` ) ;
601+ }
602+ regPattern . lastIndex = 0 ; // 再次重置以备替换
603+
591604 return strContent . replace ( regPattern , strReplacement ) ;
592605}
593606
@@ -598,7 +611,14 @@ function applyGlobalReplace(strContent: string, op: GlobalReplaceOperation) : st
598611 throw new Error ( `全局替换为空` ) ;
599612 }
600613
601- const regPattern : RegExp = new RegExp ( escapeRegExp ( op . m_strOldContent ) , 'gs' ) ;
614+ const regPattern : RegExp = new RegExp ( normalizeLineWhitespace ( escapeRegExp ( op . m_strOldContent ) ) , 'gs' ) ;
615+
616+ regPattern . lastIndex = 0 ;
617+ if ( ! regPattern . test ( strContent ) ) {
618+ throw new Error ( `全局替换失败:文件 "${ op . m_strFilePath } " 中未找到旧内容 "${ op . m_strOldContent } "。` ) ;
619+ }
620+ regPattern . lastIndex = 0 ;
621+
602622 return strContent . replace ( regPattern , op . m_strNewContent ) ;
603623}
604624
@@ -610,14 +630,21 @@ function applyInsert( strContent: string, op: InsertOperation ) : string
610630 }
611631
612632 // 将前后锚点转义后使用
613- const strBeforeRegex : string = escapeRegExp ( op . m_strBeforeAnchor ) ;
614- const strAfterRegex : string = escapeRegExp ( op . m_strAfterAnchor ) ;
633+ const strBeforeRegex : string = normalizeLineWhitespace ( escapeRegExp ( op . m_strBeforeAnchor ) ) ;
634+ const strAfterRegex : string = normalizeLineWhitespace ( escapeRegExp ( op . m_strAfterAnchor ) ) ;
615635 // 中间部分直接作为正则片段,不做转义
616- const strMiddlePattern : string = "((?: [ \\t]*(?: \\r? \\n))*) " ;
636+ const strMiddlePattern : string = "[ \\t\\r\\n]* " ;
617637
618638 const regPattern : RegExp = new RegExp ( strBeforeRegex + strMiddlePattern + strAfterRegex , 'gs' ) ;
619639 const strReplacement : string = op . m_strBeforeAnchor + op . m_strInsertContent + op . m_strAfterAnchor ;
620640
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+
621648 return strContent . replace ( regPattern , strReplacement ) ;
622649}
623650
@@ -636,17 +663,23 @@ function applyDelete( strContent: string, op: DeleteOperation ) : string
636663 // 使用捕获组 $1 和 $2 来保留匹配到的前后附加内容(不删除)
637664 const strReplacement : string = op . m_strBeforeAnchor + "$1" + "$2" + op . m_strAfterAnchor ;
638665
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+
639672 return strContent . replace ( regPattern , strReplacement ) ;
640673}
641674
642675// 根据前锚点、内容、后锚点构建正则表达式(dotall 模式)
643676function buildPattern ( strBefore : string , strContent : string , strAfter : string ) : RegExp {
644677 return new RegExp (
645- escapeRegExp ( strBefore ) +
678+ normalizeLineWhitespace ( escapeRegExp ( strBefore ) ) +
646679 '([\\s\\S]*?)' + // 捕获前锚点与旧内容之间的任意字符(非贪婪)
647- escapeRegExp ( strContent ) +
680+ normalizeLineWhitespace ( escapeRegExp ( strContent ) ) +
648681 '([\\s\\S]*?)' + // 捕获旧内容与后锚点之间的任意字符(非贪婪)
649- escapeRegExp ( strAfter ) ,
682+ normalizeLineWhitespace ( escapeRegExp ( strAfter ) ) ,
650683 'gs' // 全局匹配且允许跨行
651684 ) ;
652685}
@@ -683,6 +716,15 @@ function escapeRegExp(str: string) : string
683716 return str . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
684717}
685718
719+ function normalizeLineWhitespace ( anchor : string ) : string {
720+ return anchor . split ( '\n' )
721+ . map ( line => {
722+ // 对每一行的空白字符做更精确的处理
723+ return `\\s*${ line . trim ( ) } \\s*` ; // 保留行首和行尾的空白字符处理
724+ } )
725+ . join ( '\n' ) ; // 行与行之间允许有空白字符(空格、换行符等)
726+ }
727+
686728function filePathNormalize ( strRawPath : string ) : string
687729{
688730 return path . normalize ( strRawPath . replace ( / ^ [ \\ / ] + / , '' ) ) ;
0 commit comments