当前位置:网站首页>>电脑技术>>C、C++语言>>我的著作 双击自动滚屏
中西文全屏幕编辑程序的设计和编制(七)

发表日期:2006年1月1日      已经有4363位读者读过此文

                                   第七章    字符串的搜索和替代

    依靠人的眼睛在一篇文章里寻找一个字或字符串,有时是十分困难的,而设置了字符串搜索功能的编辑软件却可以轻而易举地做到这一点,在把文本中某一重复出现的字符串都换成另一个字符串时就更是这样。因此,为编辑程序配备字符串的搜索和替代功能是非常必要的。本章就字符串搜索和替代函数的设计方法进行一些分析。

                                7.1 字符串的搜索

    通过字符串的搜索功能可以很快地找到指定的字符串,这对提高编辑工作的效率是十分有益的,因此大部分全屏幕编辑软件都设置有字符串搜索查找功能。字符串的搜索可以设计成单向的,即从当前光标位置向文末方向的搜索,也可以设计成双向的,即从当前光标位置向文首、文末两个方向的搜索,本文仅介绍前一种设计,即从当前位置向文末的搜索。BJ 全屏幕编辑软件通过按 F5 键调用函数 F5() 实现字符串的搜索。

F5()
{
  write_ques(5);                            /* 提问要找的字符串 */
  *fnd=0;                                   /* 指针变量初始化 */
  if(key_string(HH,19,fnd,PROM_COLOR)<=0) { /* 输入字符串,如为空串或按ESC键 */
    clear_ques();                           /* 清提问区 */
    return;                                 /* 退出本功能 */
  }
  Shift_F5();                               /* 进行搜索操作 */
}

    F5() 分两部分,一部分是在屏幕的提问行提问,并输入要搜索的字符串到指针变量fnd 中去。fnd 是一个全局变量,已经在主函数 main() 里分配了内存空间。第二部分是调用函数 Shift_F5(),函数 Shift_F5() 判断 fnd 不为空串时,调用函数 find() 进行搜索,找到后显示相应屏幕。搜索到文末时,停止继续搜索,将 fnd 清为空串。

Shift_F5()                  /* 进行搜索 */
{
  if(*fnd)  {               /* 如字符串不为空串 */
    if(find(fnd)) *fnd=0;   /* 搜索,如到文末,将 fnd 清为空串 */
    comp_disp();            /* 计算参数并重显一屏 */
  }
}

    find() 是具体进行搜索的函数,形参 a 是要找的字符串。find() 先测出字符串 a 的长度放在整型变量 j 中,并通过 while() 循环把搜索范围限定在从当前行至文本最末行范围内。 搜索寻找的方法是对本行内当前位置起的 j 个字符依次和要找的字符串 a 对照,只要发现一个字符不相同,就停止比较,将当前光标位置后移一字符,重新进行比较。如果 j 个字符都一一对应相同,并且对上的串尾不在汉字前半部,则移光标至找到的字符串尾的后面,搜索即告暂停,返回 0。在一个文本行中,光标移动后,光标列后的字符数少于 a 的字符数时,不再在本行中进行比较。此时如还未到文末行,就将当前光标位置转移至下行首,继续进行比较。如已是文末行,则定光标于文末,返回 1。当然在设计 find() 时必须保证在搜索的过程中,当前行始终在编辑数组中,否则要通过两个临时文件的读写进行调节。

int find(char *a)                /* 搜索字符串,a 为要找的字符串 */
{
  int i,j,b;
  j=strlen(a);                   /* 要找的字符串长 */
  while(xx<=ttl_x)  {            /* 限定在总行数范围内找而设的循环 */
    ........
    b=string_lingth();           /* 本行除行末回车换行符外的字符数 */
    for(i=0;i<j;i++)  {          /* 对比 j 个字符 */
      if(b-yy+1<j)  {            /* 如本串剩下已不足要找的串长 */
        if(xx<ttl_x)  {          /* 如未到文末行 */
          ss_x++;    xx++;       /* 下移一行 */
          yy=0;                  /* 至行首 */
          break;                 /* 退出 for 循环 */
        }
        else  {                  /* 如到文末行 */
          yy=b;                  /* 光标至行末 */
          ........
          return  1;             /* 返回 1 */
        }
      }
      if(ss[ss_x][yy+i]!=*(a+i)) {  /* 如果有一个字符对不上 */
        yy++;                    /* 就后移一字符 */
        i=-1;                    /* 重新循环时加步长 1,i 从 0 开始 */
        ........
      }
      else if(i==j-1)  {         /* 如 j 个字符都对上 */
             if(!vs(yy+j-1)) {   /* 对上的串尾如不在汉字前半字 */
               yy+=j;            /* 当前位置移至对上的最后一字符后 */
               return  0;        /* 返回 0 */
             }
             else {              /* 否则后移一字节, 重新比较 */
               yy++;
               i=-1;
             }
           }
    }
  }
}

    在上述程序里,j 个字符都对上后,为什么还要检查对上的串尾是否在汉字的前半字呢?这是因为全角字符是用两个字节的内码表示的,在一个字符行中,一个汉字的第二个内码和后一个汉字的第一个内码的组合可能正好和另一个要搜索的汉字内码相吻合,在搜索时就会发生误判断。如在文本文件中,有一个字符串“在右面的数字小键盘中”,操作时拟搜索“中”字,但由于字符串里的“字”的第二个内码是 0xD6,“小”的第一个内码是 0xD0,正好与“中”的内码 0xD6D0 相吻合,如果不进行判断,搜索时当前光标位置就会落在“小”字的后半字,发生误判。
    F5() 输入了要找的字符串后只进行了一次搜索,亦即找到第一个相同的字符串后,把光标定位于其后。在实际编辑时,操作者往往需要继续寻找文本中其它地方出现的同样的字符串,这时可用组合键 Shift+F5。按了 Shift+F5 后,只要当前光标位置不在文末,程序直接调用函数 Shift_F5(),进行下一次搜索。

                           7.2 字符串的替代

    字符串替代功能在操作者需要把文本文件中某个重复出现的字符串全部或部分改成另一个字符串时,是十分方便的。实现这个功能的函数 F6() 如下:

F6()                                /* 字符串的替代 */
{
  int i,j,g,k;
  write_ques(7);                    /* 提问搜索和替代的字符串 */
  if(key_string(HH,6,hsz,PROM_COLOR)<=0)  {
                                    /* 输入要找的字符串,如为空串或按 ESC 键 */
    return;                         /* 退出本功能 */
  }
  ........
  if(key_string(HH,32,ddd,PROM_COLOR)<=0)  {
                                    /* 输入替代的字符串,如为空串或按 ESC 键 */
    ........
    return;                         /* 退出本功能 */
  }
  g=strlen(hsz);                    /* 计算字符串长 */
  k=strlen(ddd);                    /* 计算字符串长 */
  while(1)  {                       /* 为连续搜索替代设的循环 */
    if(find(hsz)) {                 /* 如到文末 */
      comp_disp();                  /* 计算参数,显示一屏 */
      break;                        /* 退出循环 */
    }
    xh();                           /* 在 H1 行重显当前行序、列、行数值 */
    comp_disp();                    /* 计算参数,显示一屏 */
    if((j=key_yn(59))==-1) {        /* 在提问行第 59 列输入 Y 或 N,如按 ESC */
      ........
      break;                        /* 退出本功能 */
    }
    if(j)  {                        /* 如按 Y,进行替换操作 */
      chg=1;                        /* 文件已修改标志置为真 */
      i=yy-g;                       /* 删除原字符串后光标处列号 */
      while(yy>i)  delc();          /* 删除原字符串 */
      ........
      write_block(xx);              /* ddd 中字符串拷入 */
      ddd=dd;                       /* 恢复 ddd 首指针 */
    }
  }
  free(ddd);                        /* 释放 ddd 占用的内存空间 */
}

    F6() 一开始用函数 write_ques(7) 在屏幕提问行显示提问,该提问要求回答三个问题,即要找的字符串、替换成的字符串和是否要替换,前两个问题用 key_string() 函数将输入的字符串分别存放在字符型指针变量 hsz 和 ddd 中,第三个问题要找到后才用 key_yn()函数输入回答。
    字符串替换操作是连续进行的,即从当前光标处开始向文末方向搜索,每找到一个相符的字符串,就停下来,显示当前屏幕,并要求回答一次是否替换。如回答“Y”就删除刚找到的那个字符串,用 ddd 中的字符串插入该处,如回答“N”,则不替换,继续寻找下一个和 hsz 相同的字符串,就这样不断进行,一直到文末。所以 F6() 的搜索替换过程被设置在一个无限循环 while(1) 中,只有当进行到文末或中途循问是否替换时按 Esc 键才退出循环,返回主程序。
    while(1) 中的 1 是常量,while() 循环继续执行的条件始终为真,因此在编程时必须开辟在一定条件下跳出循环的出口,否则就会成为真正的死循环,使程序无法继续正常运行下去。前面讲过的 for(;;) 无限循环和 while(1) 是等价的。
    在上一节里已经讲述了字符串搜索函数 find(),F6() 直接应用 find() 函数从当前光标位置开始向文末方向搜索 hsz 中的字符串,并用 key_yn() 回答是否替换,如 key_yn()返回值为真,则 F6() 循环调用删字函数 del() 把找到的字符串删除。如何把 ddd 中的字符串插入该处呢?我们不妨把 ddd 中的字符串看成一个读入的字块,只要借用字块写入函数 write_block() 就可以很容易地把 ddd 中的字符串插入当前位置。F6() 中调用的各函数都是前面已经用到过的,就象制造一台机器,如果已经有了许多现成的零部件,就能事半而功倍,因此,我们提倡在编制 C 语言程序时,把一个复杂的问题分成若干个小块,每个小块编一个函数,用这些小零部件,可以巧妙地组合成各种解决复杂问题的大模块。

                              第八章   排版

    对于一个经常用来进行文书处理的中文编辑软件来说,排版功能是必不可少的。许多优秀的编辑软件设计时在排版上下了很大的功夫。排版模块的设计不但要考虑文本输入后的排版,而且必须考虑在文字输入的过程中,同时进行排版的问题。

                         8.1 格式文件和非格式文件

    在设计文字编辑程序时,通常把文本文件分成格式文件和非格式文件两类。非格式文件不进行任何排版操作,有时候又把它叫做“纯文本文件”。此类文件的文本行都是以硬回车和换行符 0x0D0A 结尾的,由于文件中没有夹杂任何排版符和字体、字号、行距、列距之类的符号,也可以说没有附带不同编辑软件的“个性”,所以它对各种文字处理软件有很好的适应性。各种软件的源程序清单就是一种典型的非格式文件。
    格式文件则可以进行排版处理,把两个硬回车符之间的一个自然段,按操作者要求的行宽排列成比较整齐的格式。格式文件自然段内部的行是用软回车符和换行符来分隔的,不同的编辑软件规定的软回车符不尽一样,本文实例采用和 WS、WPS 等文字编辑软件相同的表示方法 (0x8D0A)。除此之外不少编辑软件还对文本打印的行距、列距、分页、字体、字号、打印方向、打印背景等等许多要求进行了设置,因此在编辑文本中插入了各种各样的标识符,这些符号没有统一的标准,而由编辑软件的设计者自行确定,通常利用 ASCII 码小于 32 或 127 至 160 之间的部分码值,以便和半角及全角字符明确地区分开来。近几年来出现的许多优秀的文字编辑系统编写的格式文件可以打印出非常美观实用的版面,对传统印刷业的改造和办公自动化的实现起了十分重要的作用。但也由此而带来以下问题: 一是,不同编辑软件编写的格式文件之间几乎无兼容可言,一种编辑软件编写的格式文件往往在另一种编辑软件上不能正确地显示和打印。二是编辑软件对汉字打印驱动软件,甚至于整个汉字操作系统的依赖性使得文字编辑软件不能在大多数常用汉字操作系统环境下通用,使编辑软件的使用受到限制。
    为了适应不同情况,不少编辑软件都设计有编辑格式文件、非格式文件两种工作状态,通过菜单进行选择。人们往往习惯于称格式文件为“D”型文件,而称非格式文件为“N”型文件。

                           8.2 排版行宽的设置

    本文介绍的编辑软件只设计了设定行宽排版的功能,这样在各种汉字操作系统均可使用。本文程序编辑数组设置的最大行宽 HC=255 字节,扣除字符串末的结束符 ''''\0'''' 占用的一个字节,还可容纳 254 个字节,考虑到通常格式文件不会设置这么大的行宽,所以程序设计时把软件运行初始状态时,作为“N”型文本的编辑状态,如果编辑过程中设置了行宽,程序运行自动切换到“D”型文本的编辑状态。行宽的设置按 F3 键调用函数 F3() 完成,F3()中排版行宽字节数放在变量 enq 中,enq 的初值设置为 HC-4=251,是因为考虑到为了适应汉字书写和印刷的习惯,要把一些不宜放在行首的标点符号留在原行末,而留了一些余地。

#define HC 255                         /* 编辑缓冲区数组每行最大字节数控制值 */
int enq=HC-4;                          /* 排版行宽,初值设为 HC-4 */
........
F3()                                   /* 输入排版行宽 */
{
  int i;
  write_ques(4);                       /* 提问排版行宽 */
  if((i=key_digit(16))<=0) {           /* 在提问行输入行宽,如为空串或按 ESC键 */
    clear_ques();                      /* 清提问区 */
    return;                            /* 退出本功能 */
  }
  enq=i;                               /* 行宽值赋给全局变量 enq */
  if(enq>HC-4)  {                      /* 如超过允许字节数 */
    enq=HC-4;                          /* 取最大允许字节数为排版行宽 */
    itoa(enq,hsz,10);                  /* 变数值为字符串 */
    write_prompt(7);                   /* 提示排版超宽 */
    write_space(HH,16,30,PROM_COLOR);  /* 清输入区 */
    write_string(HH,16,hsz,PROM_COLOR);/* 重显行宽 */
  }
}

    F3() 先通过 write_ques() 和 key_digit() 输入排版行宽,放在全局变量 enq 中。当操作者误将大于 HC-4 的数输入时,F3() 将行宽值置为 HC-4,并提示“排版超宽!”。由于在这种情况下,行宽 enq 的值有所调整,所以要用库函数 itoa() 将 enq 的值变为字符串,并在提问行的输入处重新显示。行宽设定后,表尺显示函数 coord() 使行宽以上的表尺改变颜色,以便操作者能从屏幕上清楚地看到排版的分界位置。
 
                          8.3 文本的排版操作

    在 5.5 节中已经说过,格式文件中两个硬回车之间的部分称为“段”,排版操作实际上是分段进行的。要完成一次从当前光标位置到本段末的排版,可按 F4 键。函数 F4() 先根据 enq是否小于 HC-4 来判断是否已设置了排版宽度,如果已经设置,则调用排版子函数 reform() 进行排版,排至段末后,显示排版后的屏幕。如果 enq 仍为初值,表明没有设置排版行宽,则提示“未设定排版宽度!”,退出本功能。

F4()                               /* 段排版 */
{
  if(enq<HC-4) {                   /* 如已重设排版行宽 */
    chg=1;                         /* 文件已修改标志置为真 */
    reform();                      /* 排版至段末 */
    comp_disp();                   /* 计算参数并重显一页 */
  }
  else  write_prompt(8);           /* 否则,提示:“未设置排版宽度!” */
}

    如果要对到文末的所有段进行排版,每按一次 F4 键,排版一段,显示一次屏幕,未免太麻烦,这时可按 Shift+F4 组合键,调用 Shift_F4() 函数。Shift_F4() 函数除了也要检查是否已设置了排版宽度外,为连续进行段排版,设置了一个 while() 循环。这个循环很特殊,它没有循环体,只是通过循环条件成立的判断式,反复调用函数 reform(),直到文末 reform() 返回值为假时,中止循环,显示屏幕。

Shift_F4()                         /* 排版至文末 */
{
  if(enq<HC-4) {                   /* 如已重设排版行宽 */
    chg=1;                         /* 文件已修改标志置为真 */
    while(reform());               /* 连续排版的循环,从当前光标行排版至文末 */
    comp_disp();                   /* 计算参数并重显一页 */
  }
  else  write_prompt(8);           /* 否则,提示:“未设置排版宽度!” */
}

    reform() 是具体实现排版的函数。排版是把段中的字符按照统一的行长进行重新排列的操作,但在实际排列时,由于半个汉字和特定标点符号保留在行末的问题,各文本行的长度并不完全按一样的长度“一刀切”,而是根据实际情况略有调整。因此 reform() 设计时考虑了以下因素:
    (1) 如果排版可能引起按汉字印刷习惯不宜放在行首的特定标点符号或空格出现在行首时,把它保留在上行末。这类标点符号主要有句号、逗号、感叹号、问号、分号、冒号及各类括号和引号的后半个。
    (2) 如果排版把一个全角字符前半字分在上行,而把后半字分到下行时,而这个全角字符又不是特定标点符号时,把该全角字符全部放在下行。
    (3) 除段末行外,其它各行以软回车换行符结束。
    (4) 必须同时计算排版引起的字块块标位置的变化。
    (5) 一段排版完后光标位置移至下一段的首行、首列,返回 1,如文末行排完,返回 0。
char ra[]={0x8D,0x0A,0};     /* 只有软回车换行符的字符串 */
........
int reform()                         /* 从当前位置排版至本段末 */
{
  int k,g;
  write_prompt(0);                   /* 提示“请稍候...” */
  for(;;)  {                         /* 为段排版设置的循环 */
    g=string_lingth();               /* 计算字符串长(不包括回车换行符) */
    if(g>enq) {                      /* 如串尾在排版宽后 */
      if(vs(enq-1)==0)  {            /* 如排版宽度处不为全角字符前半字 */
        if(ss[ss_x][enq]<0xA0) {     /* 如排版宽度后一字节为半角字符 */
          if(punc1(ss[ss_x][enq]))   /* 如为指定半角标点 */
            k=enq+1;                 /* 折断处后移 1 字节 */
          else k=enq;                /* 否则原处折断 */
        }
        else  {                      /* 如排版宽度处字符为全角 */
          if(punc2(ss[ss_x][enq],ss[ss_x][enq+1]))   /* 如为指定全角标点 */
            k=enq+2;                 /* 折断处后移 2 字节 */
          else  k=enq;               /* 否则原处折断 */
        }
      }
      else  {                        /* 如排版宽度处为全角字符前半字 */
        if(punc2(ss[ss_x][enq-1],ss[ss_x][enq]))    /* 如为指定的全角标点 */
          k=enq+1;                   /* 折断处后移一字节 */
        else k=enq-1;                /* 否则, 折断处前移一字节 */
      }
      ser+=k-yy;                     /* 计算字序数 */
      if(ss[ss_x][k]!=0x0D) {        /* 如折断处不在段末 */
        intercept(k);                /* 在 k 处折断行 */
        strcpy(ss[ss_x-1]+k,ra);     /* 折断处加软回车 */
      }
      else {                         /* 如折断处已到串尾 */
        xx++;   ss_x++;              /* 到下一行 */
        ser+=2;                      /* 计算字序数 */
        yy=0;                        /* 到行首 */
        break;                       /* 退出循环 */
      }
      yy=0;                          /* 到行首 */
    }
    else  {                          /* 如串尾在排版宽前 */
      if(xx<ttl_x) {                 /* 如未到文末 */
        ser+=g-yy+2;                 /* 计算字序数 */
        ss_x++;   xx++;              /* 到下行 */
        yy=0;   y=0;                 /* 到行首 */
        if(ss_x>ss_max)  tj();       /* 如超出缓冲区数组,读入一部分 */
        if(ss[ss_x-1][g]==0x8D) {    /* 如上行末为软回车 */
          if(delc()==-1) return 0;   /* 本行接至上行末,行超长返回 */
        }
        else  break;                 /* 否则退出循环 */
      }
      else {                         /* 如已到文末行 */
        clear_prompt();              /* 清提示区 */
        return 0;                    /* 返回 0 */
      }
    }
  }
  clear_prompt();                    /* 清提示区 */
  return 1;                          /* 返回 1 */
}

    reform() 中设置了一个无限循环 for(;;),当排版到一个硬回车时,跳出循环,返回调用函数。排版从光标所在行起,向后逐行进行。排版的方法有多种,本文的 reform() 的设计思路是先将当前行长度和设置的排版宽度比较,假如当前行行尾在排版宽度后,则用函数 intercept() 折断字符串,超出排版宽度的部分作为一个新行插在本行后,当前行改为新行。如果当前行行尾在排版宽度前,则移光标位置至下行首,用函数 delc() 将下行接至本行末,当前行仍变为本行,当光标在行首时 delc() 函数的作用就是把该行接至上行末。然后,再进行下一次比较。这种方法的好处是可以利用现有的函数 intercept() 和 delc(),而无须设计新的函数,缺点是如果设置的排版宽度很大时,排版时两行相接可能出现行超长。好在设计 del() 函数时已经考虑了行超长退回,不会造成严重的后果。
    当本行字符串尾在排版宽度后时,用 vs() 函数判断排版宽度处是否为全角字符的前半字,如果不是,则判断排版宽度后的一个字符是全角还是半角,如判定为半角字符时,将该字符作为参数,用函数 punc1() 检查是否是特定的半角标点符号,如是则将 enq 加 1,赋给存放折断处列号的变量 k,否则取 k=enq;如果判定排版宽度后的字符为全角字符时,则应再取一字节,用函数 punc2() 检查是否是特定的全角标点符号,如是,则折断处列号 k 等于 enq+2,否则取 k=enq。
    如果用 vs() 函数判断,排版宽度处是全角字符的前半字,则将其和后一字符作为参数,用 punc2() 检查,如为指定的全角标点符号,就把折断处后移一字符,将此标点符号保留行末。否则,令 k=enq-1,把折断处前移一字符,将全角字符的前半字调整到下行首。
    折断处位置确定后,把折断处列号 k 作为实参,调用函数 intercept(k) 折断字符串换行,并在原行折断处后加软回车换行符 0x8D0A。
    检查是否是特定标点符号的两个函数如下:

int punc1(unsigned char z)       /* 检查是否是指定的半角标点或空格 */
{
  if(z==33||z==41||z==44||z==46||z==58||z==59||z==63||z==93||z=32)
    return 1;                    /* 如是指定标点,返回 1 */
  else  return 0;                /* 否则返回 0 */
}

int punc2(unsigned char z1,unsigned char z2)  /* 检查是否是指定的全角标点 */
{
   if((z1==161&&(z2==162||z2==163||z2==164||z2==175||z2==177||z2==179
      ||z2==181||z2==183||z2==185||z2==187||z2==189||z2==191||z2==195))
      ||(z1==163&&(z2==161||z2==169||z2==172||z2==174||z2==186||z2==187
      ||z2==191||z2==221)))   return 1;  /* 如是,返回 1 */
   else return 0;                        /* 否则返回 0 */
}

                         8.4 字符输入时的排版

    在前面第 5.2 节已经介绍了字符输入基本操作的实现方法,下面讲述在字符输入的同时进行的排版。如果编辑时已经设置了排版行宽,则在排版宽度处或排版宽度以后输入字符时,同时进行排版操作,这和按 F4 键进行的段排版不同,它仅对输入字符的当前行进行排版。当然它同样必须遵守排版宽度处半个汉字和特定标点符号的处理原则。因此,排版主要通过调用 intercept() 函数折断字符串来完成,也就是说,排版的关键是通过对排版长度处是否会将一个全角字符分为两半和检查是否会将特定标点符号放在行首的判断,调整确定折断字符串的位置变量 k。
    以下 Chr() 函数是一个完整的字符输入函数,其中画表格线的部分读者可以暂不理会它,我们将在下一章里专门讲述。

Chr()                            /* 输入字符 */
{
  static int bb=0;               /* 定义一个静态变量,作为全角一、三区字符标志 */
  int j,g,k;
  ........
  if(cc.ch[0]>31) {              /* 屏蔽控制键 */
    chg=1;                       /* 文件已修改标志置为真 */
    qq=0;                        /* 表格线标志变量置初值 */
    make_tab();                  /* 如表线开关为开,判断按键和形成表格线 */
AA: z2=cc.ch[0];                 /* 将字符放入 z2 */
    if(yy>=enq)  {               /* 如在排版宽度以后 */
      if(vs(enq-1)==0) {         /* 如排版长度处不为全角后半部 */
        if(yy==enq) {            /* 如写在排版长度处 */
          if(z2<161)  {          /* 如为半角字符 */
            if(punc1(z2))  goto BB;   /* 如为指定标点符号,转写入字符串 */
            else k=enq;          /* 如不是,在此处折断 */
          }
          else  {                /* 如为全角字符 */
            if(z2==161 || z2==163)  {  /* 如第一字节在一、三区 */
              z1=z2;             /* 输入字符从 z2 移入 z1 暂存 */
              bb=1;              /* 全角一、三区标志置 1 */
              goto BB;           /* 先写入字符串 */
            }
            else k=enq;          /* 否则在此处折断 */
          }
        }
        else  {                  /* 如在排版长度之后 */
          if(bb && punc2(z1,z2))  goto BB;
                                 /* 如为指定全角标点符号,写入字符串 */
          else  {                /* 否则检查排版长处字符 */
            bb=0;
            a1=ss[ss_x][enq];    /* 排版长处取一字节,放入 a1 */
            if(a1<161)  {        /* 如为半角字符 */
              if(punc1(a1)) k=enq+1; /* 如为指定半角标点,折断处后移一字节 */
              else k=enq;        /* 否则原处折断 */
            }
            else  {              /* 如为全角字符 */
              a2=ss[ss_x][enq+1];/* 再取一字节放入 a2 */
              if(punc2(a1,a2))   /* 如为指定全角标点*/
                k=enq+2;         /* 折断处后移二字节 */
              else   k=enq;      /* 否则原处折断 */
            }
          }
        }
      }
      else  {                    /* 如果排版长度处为全角字符后半部 */
        z1=ss[ss_x][enq-1];      /* 取出全角前半部 */
        if(yy==enq) {            /* 如写入处在排版长度处 */
          if(punc2(z1,z2))  goto BB;   /* 如为指定全角标点, 转写入字符串 */
          else  k=enq-1;         /* 否则折断处前移一字节 */
        }
        else  {                  /* 如写入在排版长之后 */
          a2=ss[ss_x][enq];      /* 检查应折断处字符 */
          if(punc2(z1,a2))       /* 如为指定全角标点 */
            k=enq+1;             /* 折断处后移一字符 */
          else  k=enq-1;         /* 否则,折断处前移一字节 */
        }
      }
      intercept(k);              /* 折断字符串换行 */
      strcpy(ss[ss_x-1]+k,ra);   /* 折断处加软回车换行符 0x8D0A */
      yy-=k;                     /* 计算列号 */
      comp_disp();               /* 计算参数,重显当前屏幕 */
    }
BB: g=string_lingth();           /* 计算行长(不包括回车换行符) */
    if(ins || yy==g)  {          /* 如为插入状态或写在行末时 */
      if(g>HC-4)  {              /* 如行超长退回 */
        write_prompt(3);         /* 提示超长 */
        return;                  /* 返回 */
      }
      if(xx==ksx && yy<ksy)  ksy++;  /* 计算块坐标变化 */
      if(xx==kwx && yy<kwy)  kwy++;
      for(j=g+3;j>yy;j--)  ss[ss_x][j]=ss[ss_x][j-1];
    }                            /* 插入字符后的字符依次后移 */
    else {                       /* 如为非插入状态 */
      if(cc.ch[0]<127 && ss[ss_x][yy]>160)  ss[ss_x][yy+1]=32;
                                 /* 如为半角复盖全角,全角后半字填空格 */
      if(vs(yy)==0 && cc.ch[0]>160 && ss[ss_x][yy]<127 && ss[ss_x][yy+1]>160) {
                                 /* 如为全角复盖一个半角和一个全角的前半部 */
        ss[ss_x][yy+2]=32;       /* 全角后半字节填空格 */
      }
    }
    ss[ss_x][yy]=z2;             /* 将键入字符读入数组 */
    y++;                         /* 光标后移一格 */
    yy++;                        /* 后移一字节 */
    ser++;                       /* 字序号增 1 */
    if(qq)  {                    /* 如为全角制表符 */
      qq=0;                      /* 表格线标志变量置 0 */
      cc.ch[0]=two;              /* 暂存在 two 中的表格线第二字节放入cc.ch[0] */
      goto AA;                   /* 返回 AA,进行排版,并写入第二字节 */
    }
    if(vs(yy-1)) return;         /* 如为前半字节,再读入一字节 */
    if(y>=ZS)  {                 /* 如到本屏行尾 */
      m++;                       /* 后移一屏 */
      disp_t();                  /* 显示后一屏 */
    }
    else disp(ss_x,x);           /* 否则重显本行 */
  }
}

    在 Chr() 中,从 AA 处开始, 先把输入的字符放在 Z2 中,判断光标是否在排版宽度enq 后。如果在 enq 前,则转入写入字符串的操作,这在 5.2 节中介绍过了。本节讲的主要是写在 enq 处或其后时,如何进行排版操作。下面分排版长度处为全角字符后半部和不为全角字符后半部两种情况讨论。
    (1) 当排版长度处不为全角字符后半部时
    如果写在 enq 处,若写入的是半角字符 (Z2<161),则调用 punc1(Z2) 检查,如是特定标点符号,就转到写入字符串,否则令 k=enq,在该处折断字符串行。如果写入的是全角字符,当从键盘输入的第一字节判定是特定全角符号所在的国标第一或第三区的字符时,则先把该字节暂存入 z1,并写入字符串,同时置标志变量 bb=1,待接收到该全角字符的第二字节时再判断是否是特定的标点符号。当判定不是第一、三区的全角字符时,令 k=enq,从该处折断字符串。
    如果写在 enq 后,若 z2 接收的是第一、三区全角字符的第二字节 (标志 bb=2),并调用函数 punc2(z1,z2) 判定为特定标点符号,则写入字符串。否则,从排版长度处取一字节放入 a1。当 a1 为半角字符时,调用 punc1(a1) 检查是否是特定标点符号,如是,将折断处后移一个字节 (k=enq+1),将此标点符号保留在行末;如不是,令 k=enq,原处折断。当 a1 是全角字符时,再取后一字符,放入 a2,用 punc2(a1,a2) 检查和调整 k 的值。
    (2) 当排版处是全角字符后半部时
    先读出全角字符的前半字放入 z1,如果写在 enq 处,调用 punc2(z1,z2) 检查,如为特定标点符号,则保留在行末,写入字符串,否则折断处前移一字节 (令 k=enq-1),把全角前半字摆到下行首。如果写在 enq 后面,再取出排版处全角字符的后半字,放入 a2,用 punc2(z1,a2) 检查,确定 k 值。
    以上的判断结束,折断处位置 k 值确定后,便可以调用 intercept(k) 函数折断当前行,在折断的行尾加软回车换行符,并重显当前屏幕。
    在函数 Chr() 里,定义了一个静态变量 bb,并在定义时赋初值 0。这个静态变量的作用域为在 Chr() 函数中,它只初始化一次。函数运行后其值不因退出函数而消失,当再次进入该函数时,bb 不再赋初值,而仍保持着函数上一次调用时确定的值。这正符合了 Chr()函数中输入特定全角标点符号时的要求,当输入的全角第一字节表明该全角字符在汉字第一、或第三区,有可能是特定全角标点符号时,置 bb=1,当程序接收第二字节再次进入 Chr() 函数时,bb 仍保持 1。这时就可通过函数 punc2() 对这两个字节组成的全角字符进行判断,并作出相应的处理。


相关专题: 我的著作
专题信息:
  C语言速成(第三章 程序控制语句)(2006-1-1 13:51:28)[9679]
  C语言速成(第七章 联合、枚举和自定义数据类型)(2006-1-1 13:51:46)[10388]
  C语言速成(第六章 结构)(2006-1-1 13:52:05)[9056]
  C语言速成(第五章 指针)(2006-1-1 17:28:54)[4808]
  C语言速成(第四章 数组)(2006-1-1 13:52:42)[4969]

相关信息:
 没有相关信息
  打印本页
设为首页 | 加入收藏 | 联系我们 | 管理入口
皖ICP备05018956号
Copyright © 2003 J.W.SHEN All Rights Reserved
后台管理系统 V1.0 制作
皖公网安备 34018102340322号