Formats

From PostgreSQL 中文维基, PostgreSQL 中文站, PostgreSQL 中国社区, PostgreSQL Chinese community

Jump to: navigation, search

目录

[编辑] 第七章 格式

Perl 有一个机制帮助你产生简单的报告和图表。为了实现这个机制,Perl 帮助你格式化输出,使它打印出来的时候看起来比较接近于你想要的结果。它能保持跟踪象一页里面有多少行,当前的页码,以及什么时候打印页头等等的东西。使用的关键字是从 FORTRAN 里面借来的:format 用来声明而 write 用来执行;参看第二十九章 函数,获取相关内容。所幸,布局是非常易读的,很象 BASIC 中的 PRINT USING 语句。也可以将它想象 成 nroff(如果你知道 nroff,这也许不象是一个比较)。

格式输出,和包和子过程一样,是声明而不是执行,因此它们可以在你的程序中任何地方出现。(通常最好将所有的格式输出放在一起)。它们有他们自己的名字空间,与 Perl 中其它类型的名字空间是区分开来的。这就是说如果你有一个函数 "Foo",但它不同于一个名字为 "Foo" 的格式输出。一个缺省规定是,一个文件句柄和一个相关联的格式输出的缺省名字与该文件句柄的名字相同。因而,STDOUT 的缺省格式输出的名字是 "STDOUT",文件句柄 TEM P的缺省格式输出名字为 "TEMP",它们看起来是一样的,实际上是不一样的。

输出纪录格式输出象下边一样定义:


    format NAME =
    FORMLIST
    .


如果省略 NAME,上面的代码将定义格式输出 STDOUT。FORMLIST 由一些有序的行组成,每一行都是下面三种类型中的一种:

  1. 注释,以第一列为 # 来表示.
  2. 一个格式行,用来定义一个输出行的格式
  3. 参数行,用来向前面的格式行中插入值

格式行除了那些需要被替换的部分外,严格按照它们的声明输出。(注:而且那些你放进去维护列完整性的字段也如此。在一个格式行中没有任何东西可以导致字段的伸缩或者移位。你看到的列是按照 WYSIWYG 的概念分布的---假设你用的是定宽字体。就连控制字符都假设是宽度为一的字符。)格式行中每个被替换的部分分别以 @ 或者 ^ 开头。这些行不作任何形式的变量代换。@ 字段(不要同数组符号 @ 相混淆)是普通的字段。另一种字段,^ 字段,用来进行多行文本块填充。字段的长度通过在格式符号 @,^ 后跟随特定长度的 <, >,| 来定义,同时,<,>,| 还分别表示,左对齐,右对齐,居中对齐。如果变量超出定义的长度,那么它将被截断。

作为右对齐的另外一种方式,你可以使用 #(在 @ 或 ^ 后边)来指定一个数字字段。你可以在这种区域中插入一个 . 来制定小数点的位置。如果这些区域的值包含一个换行符,那么只输出换行符前面的文本。最后,特殊的字段 @* 可以被用来打印多行不截断的值;这种区域通常在一个格式行中出现。

参数行指定参数的顺序必须跟相应的格式行的字段顺序一致。不同参数的表达式需要使用逗号分隔。参数行被处理之前所有的参数表达式都在列表环境中求值,因此单个列表表达式会产生多个列表元素。通过使用圆括弧将表达式括起来,可以使表达式扩展到多行 (因此,圆括弧必须是第一行的第一个标志)。这样就可以将值同相应的格式域对应起来方便阅读。

如果一个表达式求出的值是一个有小数部分的数字,并且如果对应的格式域指定了输出的小数部分的格式(除了没有内嵌 . 的多个 # 字符的格式),用来表示小数点的字符总是由LC_NUMERIC 区域参数确定。这就是,如果运行时环境恰好是德国本地化参数,句点会被逗号替代。参看 perllocale 手册页获取更多的内容。

在一个表达式中,空白字符 \n,\t,和 \f 总是被解释成单个空格。因而,你可以认为这样的过滤表达式作用于每个格式中的表达式:

  $value =~ tr/\n\t\f/ /;


余下的空白字符,\r, 如果格式行允许的话,将强制输出一个换行符。

以 ^ 开头的格式字段不同于 @ 格式字段,它会被特殊对待。例如一个 # 区域,如果值没有定义,那么这个区域将变为空白。对于其他的区域类型,^ 会使用一种特殊的填充模式。提供的值必须是一个包含字符串的标量变量名,而不是一个强制表达式。Perl 在这个区域中放入尽可能多的文本,并且将已经打印过的字符截去,这样当下次引用该变量的时候,就可以打印更多的文本。(这就是说在 write 调用过程中,变量本身将发生变化,原来的值将不被保留。因此如果你想保持最初的值,你需要使用一个临时变量来代替原来的变量)。 通常你应该使用一组垂直对齐的格式区域打印一块文本。你也许会想用文本 "..." 来结束最后的区域,这样当文本太长不能完整地打印的时候,就可以打印"..."。你也可以改变变量 $:(当你使用 English 模块,那么就是 $FORMAT_LINE_BREAK_CHARACTERS)来改变用来表示中断的合法字符。

使用 ^ 字段能够产生变长的纪录.如果格式字段太短,你可以重复几次带有 ^ 字段的格式行。如果用这个方法处理一块较短的数据,那么将会得到几个空白输出行。为了避免空白行,你可以在格式行中的任意地方放置一个 ~ (波浪号)。(在输出中波浪号本身会被转换成一个空格)。如果你使用第二个 ~ 波浪号,该格式行会被重复直到在该格式行中所有字段中的文本被用尽为止。(因为 ^ 字段会吃掉所要打印的字符串,因此前面的格式行能够 运行,但是如果你使用和两个波浪号结合的一个 @ 字段,你最好每次给这表达式不同的值!使用 shift 或者其他带有副作用的操作符,来用尽所有的值。)

标头的处理缺省使用当前文件句柄名加上 _TOP 后缀的格式来处理。它在每一页的开头触发。参看二十九章的 write。


  # a report on the /etc/passwd file
  format STDOUT_TOP =
                           Passwd File
  Name                Login    Office   Uid   Gid Home
  ------------------------------------------------------------------
  .
  format STDOUT =
  @<<<<<<<<<<<<<<<<<< @||||||| @<<<<<<@>>>> @>>>> @<<<<<<<<<<<<<<<<<
  $name,              $login,  $office,$uid,$gid, $home
  .
  # a report from a bug report form
  format STDOUT_TOP =
                           Bug Reports
  @<<<<<<<<<<<<<<<<<<<<<<<     @|||         @>>>>>>>>>>>>>>>>>>>>>>>
  $system,                      $%,         $date
  ------------------------------------------------------------------
  .
  format STDOUT =
  Subject: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
           $subject
  Index: @<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
         $index,                       $description
  Priority: @<<<<<<<<<< Date: @<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            $priority,        $date,   $description
  From: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        $from,                         $description
  Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
               $programmer,            $description
  ~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                       $description
  ~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                       $description
  ~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                       $description
  ~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                       $description
  ~                                    ^<<<<<<<<<<<<<<<<<<<<<<<...
                                       $description
  .

除非一个格式是在词法变量的作用范围内定义,否则该词法变量在格式中是不可见的。

在同一个输出通道中将 print 和 write 混合起来是可以的,但是你必须自己操作特殊变量$- (在 English 模块中是 $FORMAT_LINES_LEFT)。


[编辑] 格式变量

当前的格式名字存储在 $~ 中 ($FORMAT_NAME),当前的表头格式名字存储在 $^ ($FORMAT_TOP_NAME)。当前输出的页号在 $% ($FORMAT_PAGE_NUMBER),每页中的行数在$= ($FORMAT_LINES_PER_PAGE).是否自动刷新输出缓冲区存储在 $| ($FORMAT_AUTOFLUSH)。在每一页(除了第一页)表头之前需要输出的字符串存储在 $^L ($FORMAT_FORMFEED)。这些变量以文件句柄为基础设定,因此你需要 select 与特定格式关联的文件句柄来影响这些格式变量:

  select((select(OUTF),
     $~ = "My_Other_Format",
     $^ = "My_Top_Format"
     )[0]);


是不是很难看?可是这是一个习惯用法,因此当你看见它时不要感到惊讶。你至少可以使用一个临时变量来保持前一个文件句柄:

  $ofh = select(OUTF);
  $~   = "My_Other_Format";
  $^   = "My_Top_Format";
  select($ofh);


通常这是一个更好的方式,因为这不仅仅是增加了可读性,还因为你现在在代码里有了一个中间语句,这样你可以在单步调试的时候可以在这里停下来,如果你使用 English 模块,你甚至可以这样读取变量名字:

  use English;
  $ofh = select(OUTF);
  $FORMAT_NAME    = "My_Other_Format";
  $FORMAT_TOP_name = "My_Top_Format";
  select($ofh);


但是你仍然要调用这些 select,如果你想避免使用他们,使用 Perl 集成的 FileHandle 模块。现在你就可以使用小写的方法名来访问这些特殊变量:

  use FileHandle;
  OUTF->format_name("My_Other_Format");
  OUTF->format_top_name("My_Top_Format");

这样看起来更好!

因为跟在格式行后面的数值行可以包含任意的表达式(提供给 @ 字段,而不是 ^ 字段),所以你可以使用一些高级的处理,象 sprintf 或者一个你自己的函数。例如为了在一个数字里面插入一些逗号,你可以使用下面的方法:


  format Ident = 
      @<<<<<<<<<<<<<<<
      commify($n)
  .

为了在格式区域的实际输出中得到一个真的 @,~ 或者 ^,可以象下面一样:

  format Ident = 
  I have an @ here.
           "@"
  .

将整行文本居中,可以使用下面的方法:

  format Ident = 
  @||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
                            "Some text line"
  .


> 字段长度标识保证在字段中的文本将是右对齐,但只是在你定义的字段内精确地对齐。Perl 中没有一种内置的方法可以获得 "将这个区域在页右侧对齐,而不管它的宽度" 这样的效果,你必须指定用于对齐的左侧位置。你可以基于当前列号(不提供)来产生它们自己的格式,来达到上面的目的,然后 eval 它:

  $format  = "format STDOUT = \n"
           . '^' . '<' x $cols . "\n"
           . '$entry' . "\n"
           . "\t^" . "<" x ($cols-8) . "~~\n"
           . '$entry' . "\n"
           . ".\n";
  print $format if $Debugging;
  eval $format; 
  die $@ if $@;

上边最重要的行恐怕就是 print。print 将打印出下边这样的格式:

  format STDOUT = 
  ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  $entry
      ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
  $entry
  .

这里有一个小程序来达到 fmt(1) Unix 工具的功能:

  format = 
  ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ~~
  $_
  
  .
  
  $/ = "";
  while (<>) {
      s/\s*\n\s*/ /g;
      write;
  }


[编辑] 页脚

$^ ($FORMAT_TOP_NAME) 包含了当前表头格式的名称,目前没有对应的机制来自动获得页脚的定义。除非你认为它是一个主要问题,否则你不会知道一个格式将会有多大。它现在已经在我们的 TODO 列表里面。(注:不过这并不能保证我们就一定会做它。格式在 WWW,Unicode,XML,XSLT,和任何它们后来的事物占统治地位的时代显得有些过时了。)

这里由一个策略:如果你有一个给定尺寸的页脚,你可以在每个 write 之前通过检查 $- ($FORMAT_LINES_LEFT) 来获得页脚,然后你自己打印页脚。

也有另一个策略:使用 open(MESELF, "|-") 打开一个指向你自己的管道并且总是 write 到 MESELF 而不是 STDOUT。让你的子过程处理它的 STDIN 来重新安排页头和页脚。这种方法不是很方便,但是的确能跑。


[编辑] 访问格式的内部

为了对内部格式化机制进行低级访问,你可以使用内置的 formline 操作符和直接访问 $^A ($ACCUMULATOR 变量)。(格式最终编译成为一系列的对 formline 的调用)例如:

  $str = formline <<'END', 1,2,3;
  @<<<  @|||  @>>>
  END
  
  print "Wow, I just stored `$^A' in the accumulator!\n";

或者创建一个 swrite 子过程,它对于 write 的作用就像 sprintf 对 printf 的作用,可以象下边的代码一样使用:

  use Carp;
  sub swrite {
     croak "usage: swrite PICTURE ARGS" unless @_;
     my $format = shift;
     $^A = "";
     formline($format, @_);
     return $^A;
  }
  $string = swrite(<<'END', 1, 2, 3);
  Check me out
  @<<<  @||| @>>>
  END
  print $string;


如果用 FileHandle 模块,则可以使用象下边代码一样使用 formline,使一块文本在第 72 列处折行。

  use FileHandle;
  STDOUT->formline("^" . ("<" x 72 ) . "~~\n", $long_text);
Personal tools