posts - 14, comments - 0, trackbacks - 0, articles - 0

2007年1月11日

1、快速入门

(1)模板 + 数据模型 = 输出
FreeMarker基于设计者和程序员是具有不同专业技能的不同个体的观念。他们是分工劳动的:设计者专注于表示——创建HTML文件、图片、Web页面的其它可视化方面;程序员创建系统,生成设计页面要显示的数据。经常会遇到的问题是:在Web页面(或其它类型的文档)中显示的信息在设计页面时是无效的,是基于动态数据的。在这里,你可以在HTML(或其它要输出的文本)中加入一些特定指令,FreeMarker会在输出页面给最终用户时,用适当的数据替代这些代码。
下面是一个例子:

<html>
<head>
 <title>Welcome!</title>
</head>

<body>
 <h1>Welcome ${user}!</h1>
 <p>Our latest product:
 <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>

</html>

这个例子是在简单的HTML中加入了一些由${…}包围的特定代码,这些特定代码是FreeMarker的指令,而包含FreeMarker的指令的文件就称为模板(Template)。至于user、latestProduct.url和latestProduct.name来自于数据模型(data model)。数据模型由程序员编程来创建,向模板提供变化的信息,这些信息来自于数据库、文件,甚至于在程序中直接生成。模板设计者不关心数据从那儿来,只知道使用已经建立的数据模型
下面是一个可能的数据模型:

(root)
 |
 +- user = "Big Joe"
 |
 +- latestProduct
     |
     +- url = "products/greenmouse.html"
     |
     +- name = "green mouse"
数据模型类似于计算机的文件系统,latestProduct可以看作是目录,而user、url和name看作是文件,url和name文件位于latestProduct目录中(这只是一个比喻,实际并不存在)。当FreeMarker将上面的数据模型合并到模板中,就创建了下面的输出:

<html>
<head>
 <title>Welcome!</title>
</head>

<body>
 <h1>Welcome Big Joe!</h1>
 <p>Our latest product:
 <a href="products/greenmouse.html">green mouse</a>!
</body>

</html> 

(2)数据模型
典型的数据模型是树型结构,可以任意复杂和深层次,如下面的例子:

(root)
 |
 +- animals
 |   |
 |   +- mouse
 |   |   |  
 |   |   +- size = "small"
 |   |   |  
 |   |   +- price = 50
 |   |
 |   +- elephant
 |   |   |  
 |   |   +- size = "large"
 |   |   |  
 |   |   +- price = 5000
 |   |
 |   +- python
 |       |  
 |       +- size = "medium"
 |       |  
 |       +- price = 4999
 |
 +- test = "It is a test"
 |
 +- whatnot
     |
     +- because = "don't know"
类似于目录的变量称为hashes,包含保存下级变量的唯一的查询名字。类似于文件的变量称为scalars,保存单值scalars保存的值有两种类型:字符串(用引号括起,可以是单引号或双引号)和数字(不要用引号将数字括起,这会作为字符串处理)。对scalars的访问从root开始,各部分用“.”分隔,如animals.mouse.price。另外一种变量是sequences,和hashes类似,只是不使用变量名字,而使用数字索引,如下面的例子:

(root)
 |
 +- animals
 |   |
 |   +- (1st)
 |   |   |
 |   |   +- name = "mouse"
 |   |   |
 |   |   +- size = "small"
 |   |   |
 |   |   +- price = 50
 |   |
 |   +- (2nd)
 |   |   |
 |   |   +- name = "elephant"
 |   |   |
 |   |   +- size = "large"
 |   |   |
 |   |   +- price = 5000
 |   |
 |   +- (3rd)
 |       |
 |       +- name = "python"
 |       |
 |       +- size = "medium"
 |       |
 |       +- price = 4999
 |
 +- whatnot
     |
     +- fruits
         |
         +- (1st) = "orange"
         |
         +- (2nd) = "banana"
这种对scalars的访问使用索引,如animals[0].name

(3)模板
在FreeMarker模板中可以包括下面三种特定部分:

${…}:称为interpolations,FreeMarker会在输出时用实际值进行替代

FTL标记(FreeMarker模板语言标记):类似于HTML标记,为了与HTML标记区分,用#开始(有些以@开始,在后面叙述)

注释:包含在<#--和-->(而不是<!--和-->)之间
下面是一些使用指令的例子:

if指令

<#if animals.python.price < animals.elephant.price>
 Pythons are cheaper than elephants today.

<#else>
 Pythons are not cheaper than elephants today.

</#if> 

list指令

<p>We have these animals:

<table border=1>
 <tr><th>Name<th>Price
 <#list animals as being>
 <tr><td>${being.name}<td>${being.price} Euros
 </#list>

</table> 

输出为:

<p>We have these animals:

<table border=1>
 <tr><th>Name<th>Price
 <tr><td>mouse<td>50 Euros
 <tr><td>elephant<td>5000 Euros
 <tr><td>python<td>4999 Euros

</table> 

include指令

<html>

<head>
 <title>Test page</title>

</head>

<body>
 <h1>Test page</h1>
 <p>Blah blah...

<#include "/copyright_footer.html">

</body>

</html> 

一起使用指令

<p>We have these animals:

<table border=1>
 <tr><th>Name<th>Price
 <#list animals as being>
 <tr>
   <td>
     <#if being.size = "large"><b></#if>
     ${being.name}
     <#if being.size = "large"></b></#if>
   <td>${being.price} Euros
 </#list>

</table>

2、数据模型

(1)基础
在快速入门中介绍了在模板中使用的三种基本对象类型:scalars、hashes 和sequences,其实还可以有其它更多的能力:

scalars:存储单值

hashes:充当其它对象的容器,每个都关联一个唯一的查询名字

sequences:充当其它对象的容器,按次序访问

方法:通过传递的参数进行计算,以新对象返回结果

用户自定义FTL标记:宏和变换器
通常每个变量只具有上述的一种能力,但一个变量可以具有多个上述能力,如下面的例子:

(root)
|
+- mouse = "Yerri"
    |
    +- age = 12
    |
    +- color = "brown"> 
mouse既是scalars又是hashes,将上面的数据模型合并到下面的模板:

${mouse}       <#-- use mouse as scalar -->

${mouse.age}   <#-- use mouse as hash -->

${mouse.color} <#-- use mouse as hash --> 
输出结果是:

Yerri

12

brown 

(2)Scalar变量
Scalar变量存储单值,可以是:

字符串:简单文本,在模板中使用引号(单引号或双引号)括起

数字:在模板中直接使用数字值

日期:存储日期/时间相关的数据,可以是日期、时间或日期-时间(Timestamp);通常情况,日期值由程序员加到数据模型中,设计者只需要显示它们

布尔值:true或false,通常在<#if …>标记中使用

(3)hashes 、sequences和集合
有些变量不包含任何可显示的内容,而是作为容器包含其它变量,者有两种类型:

hashes:具有一个唯一的查询名字和它包含的每个变量相关联

sequences:使用数字和它包含的每个变量相关联,索引值从0开始
集合变量通常类似sequences,除非无法访问它的大小和不能使用索引来获得它的子变量;集合可以看作只能由<#list …>指令使用的受限sequences

(4)方法
方法变量通常是基于给出的参数计算值
下面的例子假设程序员已经将方法变量avg放到数据模型中,用来计算数字平均值:

The average of 3 and 5 is: ${avg(3, 5)}

The average of 6 and 10 and 20 is: ${avg(6, 10, 20)}

The average of the price of python and elephant is: ${avg(animals.python.price, animals.elephant.price)}

(5)宏和变换器
宏和变换器变量是用户自定义指令(自定义FTL标记),会在后面讲述这些高级特性

(6)节点
节点变量表示为树型结构中的一个节点,通常在XML处理中使用,会在后面的专门章节中讲述


3、模板

(1)整体结构
模板使用FTL(FreeMarker模板语言)编写,是下面各部分的一个组合:

文本:直接输出

Interpolation:由${和},或#{和}来限定,计算值替代输出

FTL标记:FreeMarker指令,和HTML标记类似,名字前加#予以区分,不会输出

注释:由<#--和-->限定,不会输出
下面是以一个具体模板例子:

<html>[BR]

<head>[BR]
 <title>Welcome!</title>[BR]

</head>[BR]

<body>[BR]
 <#-- Greet the user with his/her name -->[BR]
 <h1>Welcome ${user}!</h1>[BR]
 <p>We have these animals:[BR]
 <ul>[BR]
 <#list animals as being>[BR]
   <li>${being.name} for ${being.price} Euros[BR]
 </#list>[BR]
 </ul>[BR]

</body>[BR]

</html> 
[BR]是用于换行的特殊字符序列
注意事项:

FTL区分大小写,所以list是正确的FTL指令,而List不是;${name}和${NAME}是不同的

Interpolation只能在文本中使用

FTL标记不能位于另一个FTL标记内部,例如:

<#if <#include 'foo'>='bar'>...</if>

注释可以位于FTL标记和Interpolation内部,如下面的例子:

<h1>Welcome ${user <#-- The name of user -->}!</h1>[BR]

<p>We have these animals:[BR]

<ul>[BR]

<#list <#-- some comment... --> animals as <#-- again... --> being>[BR]

... 

多余的空白字符会在模板输出时移除

(2)指令
在FreeMarker中,使用FTL标记引用指令
有三种FTL标记,这和HTML标记是类似的:

开始标记:<#directivename parameters>

结束标记:</#directivename>

空内容指令标记:<#directivename parameters/>
有两种类型的指令:预定义指令和用户定义指令
用户定义指令要使用@替换#,如<@mydirective>...</@mydirective>(会在后面讲述)
FTL标记不能够交叉,而应该正确的嵌套,如下面的代码是错误的:

<ul>

<#list animals as being>
 <li>${being.name} for ${being.price} Euros
 <#if use = "Big Joe">
    (except for you)

</#list>

</#if> <#-- WRONG! -->

</ul> 
如果使用不存在的指令,FreeMarker不会使用模板输出,而是产生一个错误消息
FreeMarker会忽略FTL标记中的空白字符,如下面的例子:

<#list[BR]
 animals       as[BR]
    being[BR]

>[BR]

${being.name} for ${being.price} Euros[BR]

</#list    > 
但是,<、</和指令之间不允许有空白字符

(3)表达式
直接指定值

字符串

使用单引号或双引号限定

如果包含特殊字符需要转义,如下面的例子:

${"It's \"quoted\" and

this is a backslash: \\"}


${'It\'s "quoted" and

this is a backslash: \\'}

输出结果是:

It's "quoted" and

this is a backslash: \


It's "quoted" and

this is a backslash: \

下面是支持的转义序列:

转义序列
 

含义

\"
 

双引号(u0022)

\'
 

单引号(u0027)

\\
 

反斜杠(u005C)

\n
 

换行(u000A)

\r
 

Return (u000D)

\t
 

Tab (u0009)

\b
 

Backspace (u0008)

\f
 

Form feed (u000C)

\l
 

<

\g
 

>

\a
 

&

\{
 

{

\xCode
 

4位16进制Unicode代码

有一类特殊的字符串称为raw字符串,被认为是纯文本,其中的\和{等不具有特殊含义,该类字符串在引号前面加r,下面是一个例子:

${r"${foo}"}

${r"C:\foo\bar"} 

输出的结果是:

${foo}

C:\foo\bar 

数字

直接输入,不需要引号

精度数字使用“.”分隔,不能使用分组符号

目前版本不支持科学计数法,所以“1E3”是错误的

不能省略小数点前面的0,所以“.5”是错误的

数字8、+8、08和8.00都是相同的

布尔值

true和false,不使用引号

序列

由逗号分隔的子变量列表,由方括号限定,下面是一个例子:

<#list ["winter", "spring", "summer", "autumn"] as x>

${x}

</#list>

输出的结果是:

winter

spring

summer

autumn

列表的项目是表达式,所以可以有下面的例子:

[2 + 2, [1, 2, 3, 4], "whatnot"]

可以使用数字范围定义数字序列,例如2..5等同于[2, 3, 4, 5],但是更有效率,注意数字范围没有方括号

可以定义反递增的数字范围,如5..2

散列(hash)

由逗号分隔的键/值列表,由大括号限定,键和值之间用冒号分隔,下面是一个例子:

{"name":"green mouse", "price":150}

键和值都是表达式,但是键必须是字符串
获取变量

顶层变量: ${variable},变量名只能是字母、数字、下划线、$、@和#的组合,且不能以数字开头

从散列中获取数据

可以使用点语法或方括号语法,假设有下面的数据模型:

(root)
|
+- book
|   |
|   +- title = "Breeding green mouses"
|   |
|   +- author
|       |
|       +- name = "Julia Smith"
|       |
|       +- info = "Biologist, 1923-1985, Canada"
|
+- test = "title"

下面都是等价的:

book.author.name

book["author"].name

book.author.["name"]

book["author"]["name"]

使用点语法,变量名字有顶层变量一样的限制,但方括号语法没有该限制,因为名字是任意表达式的结果

从序列获得数据:和散列的方括号语法语法一样,只是方括号中的表达式值必须是数字;注意:第一个项目的索引是0

序列片断:使用[startIndex..endIndex]语法,从序列中获得序列片断(也是序列);startIndex和endIndex是结果为数字的表达式

特殊变量:FreeMarker内定义变量,使用.variablename语法访问


字符串操作

Interpolation(或连接操作)

可以使用${..}(或#{..})在文本部分插入表达式的值,例如:

${"Hello ${user}!"}

${"${user}${user}${user}${user}"} 

可以使用+操作符获得同样的结果

${"Hello " + user + "!"}

${user + user + user + user}

${..}只能用于文本部分,下面的代码是错误的:

<#if ${isBig}>Wow!</#if>

<#if "${isBig}">Wow!</#if>

应该写成:

<#if isBig>Wow!</#if>

子串

例子(假设user的值为“Big Joe”):

${user[0]}${user[4]}

${user[1..4]}

结果是(注意第一个字符的索引是0):

BJ

ig J
序列操作

连接操作:和字符串一样,使用+,下面是一个例子:

<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>

- ${user}

</#list>

输出结果是:

- Joe

- Fred

- Julia

- Kate
散列操作

连接操作:和字符串一样,使用+,如果具有相同的key,右边的值替代左边的值,例如:

<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>

- Joe is ${ages.Joe}

- Fred is ${ages.Fred}

- Julia is ${ages.Julia} 

输出结果是:

- Joe is 30

- Fred is 25

- Julia is 18 
算术运算

+、-、×、/、%,下面是一个例子:

${x * x - 100}

${x / 2}

${12 % 10}

输出结果是(假设x为5):

-75

2.5

操作符两边必须是数字,因此下面的代码是错误的:

${3 * "5"} <#-- WRONG! --> 

使用+操作符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符串,例如:

${3 + "5"} 

输出结果是:

35

使用内建的int(后面讲述)获得整数部分,例如:

${(x/2)?int}

${1.1?int}

${1.999?int}

${-1.1?int}

${-1.999?int}

输出结果是(假设x为5):

2

1

1

-1

-1
比较操作符

使用=(或==,完全相等)测试两个值是否相等,使用!= 测试两个值是否不相等

=和!=两边必须是相同类型的值,否则会产生错误,例如<#if 1 = "1">会引起错误

Freemarker是精确比较,所以对"x"、"x  "和"X"是不相等的

对数字和日期可以使用<、<=、>和>=,但不能用于字符串

由于Freemarker会将>解释成FTL标记的结束字符,所以对于>和>=可以使用括号来避免这种情况,例如<#if (x > y)>

另一种替代的方法是,使用lt、lte、gt和gte来替代<、<=、>和>=
逻辑操作符

&&(and)、||(or)、!(not),只能用于布尔值,否则会产生错误

例子:

<#if x < 12 && color = "green">
 We have less than 12 things, and they are green.

</#if>

<#if !hot> <#-- here hot must be a boolean -->
 It's not hot.

</#if> 
内建函数

内建函数的用法类似访问散列的子变量,只是使用“?”替代“.”,下面列出常用的一些函数

字符串使用的:

html:对字符串进行HTML编码

cap_first:使字符串第一个字母大写

lower_case:将字符串转换成小写

upper_case:将字符串转换成大写

trim:去掉字符串前后的空白字符

序列使用的:

size:获得序列中元素的数目

数字使用的:

int:取得数字的整数部分(如-1.9?int的结果是-1)

例子(假设test保存字符串"Tom & Jerry"):

${test?html}

${test?upper_case?html}

输出结果是:

Tom &amp; Jerry

TOM &amp; JERRY 
操作符优先顺序

操作符组
 

操作符

后缀
 

[subvarName] [subStringRange] . (methodParams)

一元
 

+expr、-expr、!

内建
 

?

乘法
 

*、 / 、%

加法
 

+、-

关系
 

<、>、<=、>=(lt、lte、gt、gte)

相等
 

==(=)、!=

逻辑and
 

&&

逻辑or
 

||

数字范围
 

..

(4)Interpolation
Interpolation有两种类型:

通用Interpolation:${expr}

数字Interpolation:#{expr}或#{expr; format}
注意:Interpolation只能用于文本部分
通用Interpolation

插入字符串值:直接输出表达式结果

插入数字值:根据缺省格式(由#setting指令设置)将表达式结果转换成文本输出;可以使用内建函数string格式化单个Interpolation,下面是一个例子:

<#setting number_format="currency"/>

<#assign answer=42/>

${answer}

${answer?string}  <#-- the same as ${answer} -->

${answer?string.number}

${answer?string.currency}

${answer?string.percent}

输出结果是:

$42.00

$42.00

42

$42.00

4,200%

插入日期值:根据缺省格式(由#setting指令设置)将表达式结果转换成文本输出;可以使用内建函数string格式化单个Interpolation,下面是一个使用格式模式的例子:

${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")}

${lastUpdated?string("EEE, MMM d, ''yy")}

${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")} 

输出的结果类似下面的格式:

2003-04-08 21:24:44 Pacific Daylight Time

Tue, Apr 8, '03

Tuesday, April 08, 2003, 09:24:44 PM (PDT)

插入布尔值:根据缺省格式(由#setting指令设置)将表达式结果转换成文本输出;可以使用内建函数string格式化单个Interpolation,下面是一个例子:

<#assign foo=true/>

${foo?string("yes", "no")}

输出结果是:

yes
数字Interpolation的#{expr; format}形式可以用来格式化数字,format可以是:

mX:小数部分最小X位

MX:小数部分最大X位

例子:
          <#-- If the language is US English the output is: -->

<#assign x=2.582/>

<#assign y=4/>

#{x; M2}   <#-- 2.58 -->

#{y; M2}   <#-- 4    -->

#{x; m1}   <#-- 2.6 -->

#{y; m1}   <#-- 4.0 -->

#{x; m1M2} <#-- 2.58 -->

#{y; m1M2} <#-- 4.0  --> 


4、杂项

(1)用户定义指令
宏和变换器变量是两种不同类型的用户定义指令,它们之间的区别是宏是在模板中使用macro指令定义,而变换器是在模板外由程序定义,这里只介绍宏
基本用法

宏是和某个变量关联的模板片断,以便在模板中通过用户定义指令使用该变量,下面是一个例子:

<#macro greet>
 <font size="+2">Hello Joe!</font>

</#macro> 

作为用户定义指令使用宏变量时,使用@替代FTL标记中的#

<@greet></@greet>

如果没有体内容,也可以使用:

<@greet/>
参数

在macro指令中可以在宏变量之后定义参数,如:

<#macro greet person>
 <font size="+2">Hello ${person}!</font>

</#macro>

可以这样使用这个宏变量:

<@greet person="Fred"/> and <@greet person="Batman"/>

输出结果是:
 <font size="+2">Hello Fred!</font>
and   <font size="+2">Hello Batman!</font>
 

宏的参数是FTL表达式,所以下面的代码具有不同的意思:

<@greet person=Fred/>

这意味着将Fred变量的值传给person参数,该值不仅是字符串,还可以是其它类型,甚至是复杂的表达式

宏可以有多参数,下面是一个例子:

<#macro greet person color>
 <font size="+2" color="${color}">Hello ${person}!</font>

</#macro>

可以这样使用该宏变量:

<@greet person="Fred" color="black"/>

其中参数的次序是无关的,因此下面是等价的:

<@greet color="black" person="Fred"/>

只能使用在macro指令中定义的参数,并且对所有参数赋值,所以下面的代码是错误的:

<@greet person="Fred" color="black" background="green"/>

<@greet person="Fred"/>

可以在定义参数时指定缺省值,如:

<#macro greet person color="black">
 <font size="+2" color="${color}">Hello ${person}!</font>

</#macro> 

这样<@greet person="Fred"/>就正确了

宏的参数是局部变量,只能在宏定义中有效
嵌套内容

用户定义指令可以有嵌套内容,使用<#nested>指令执行指令开始和结束标记之间的模板片断

例子:

<#macro border>
 <table border=4 cellspacing=0 cellpadding=4><tr><td>
   <#nested>
 </tr></td></table>

</#macro> 

这样使用该宏变量:

<@border>The bordered text</@border>

输出结果:
 <table border=4 cellspacing=0 cellpadding=4><tr><td>
   The bordered text
 </tr></td></table>
 

<#nested>指令可以被多次调用,例如:

<#macro do_thrice>
 <#nested>
 <#nested>
 <#nested>

</#macro>

<@do_thrice>
 Anything.

</@do_thrice

输出结果:
 Anything.
 Anything.
 Anything.

嵌套内容可以是有效的FTL,下面是一个有些复杂的例子:

<@border>
 <ul>
 <@do_thrice>
   <li><@greet person="Joe"/>
 </@do_thrice>
 </ul>

</@border>

输出结果:
 <table border=4 cellspacing=0 cellpadding=4><tr><td>
     <ul>
   <li><font size="+2">Hello Joe!</font>

   <li><font size="+2">Hello Joe!</font>

   <li><font size="+2">Hello Joe!</font>

 </ul>

 </tr></td></table> 

宏定义中的局部变量对嵌套内容是不可见的,例如:

<#macro repeat count>
 <#local y = "test">
 <#list 1..count as x>
   ${y} ${count}/${x}: <#nested>
 </#list>

</#macro>

<@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")}</@repeat>

输出结果:
   test 3/1: ? ? ?
   test 3/2: ? ? ?
   test 3/3: ? ? ?

在宏定义中使用循环变量

用户定义指令可以有循环变量,通常用于重复嵌套内容,基本用法是:作为nested指令的参数传递循环变量的实际值,而在调用用户定义指令时,在<@…>开始标记的参数后面指定循环变量的名字

例子:

<#macro repeat count>
 <#list 1..count as x>
   <#nested x, x/2, x==count>
 </#list>

</#macro>

<@repeat count=4 ; c, halfc, last>
 ${c}. ${halfc}<#if last> Last!</#if>

</@repeat

输出结果:
 1. 0.5
 2. 1
 3. 1.5
 4. 2 Last!
 

指定的循环变量的数目和用户定义指令开始标记指定的不同不会有问题

调用时少指定循环变量,则多指定的值不可见

调用时多指定循环变量,多余的循环变量不会被创建

(2)在模板中定义变量
在模板中定义的变量有三种类型:

plain变量:可以在模板的任何地方访问,包括使用include指令插入的模板,使用assign指令创建和替换

局部变量:在宏定义体中有效,使用local指令创建和替换

循环变量:只能存在于指令的嵌套内容,由指令(如list)自动创建;宏的参数是局部变量,而不是循环变量
局部变量隐藏(而不是覆盖)同名的plain变量;循环变量隐藏同名的局部变量和plain变量,下面是一个例子:

<#assign x = "plain">

1. ${x}  <#-- we see the plain var. here -->

<@test/>

6. ${x}  <#-- the value of plain var. was not changed -->

<#list ["loop"] as x>
   7. ${x}  <#-- now the loop var. hides the plain var. -->
   <#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here -->
   8. ${x}  <#-- it still hides the plain var. -->

</#list>

9. ${x}  <#-- the new value of plain var. -->


<#macro test>
 2. ${x}  <#-- we still see the plain var. here -->
 <#local x = "local">
 3. ${x}  <#-- now the local var. hides it -->
 <#list ["loop"] as x>
   4. ${x}  <#-- now the loop var. hides the local var. -->
 </#list>
 5. ${x}  <#-- now we see the local var. again -->

</#macro> 

输出结果:

1. plain
 2. plain
 3. local
   4. loop
 5. local

6. plain
   7. loop
   8. loop

9. plain2

内部循环变量隐藏同名的外部循环变量,如:

<#list ["loop 1"] as x>
 ${x}
 <#list ["loop 2"] as x>
   ${x}
   <#list ["loop 3"] as x>
     ${x}
   </#list>
   ${x}
 </#list>
 ${x}

</#list>

输出结果:
 loop 1
   loop 2
     loop 3
   loop 2
 loop 1
模板中的变量会隐藏(而不是覆盖)数据模型中同名变量,如果需要访问数据模型中的同名变量,使用特殊变量global,下面的例子假设数据模型中的user的值是Big Joe:

<#assign user = "Joe Hider">

${user}          <#-- prints: Joe Hider -->

${.globals.user} <#-- prints: Big Joe --> 

(3)名字空间
通常情况,只使用一个名字空间,称为主名字空间
为了创建可重用的宏、变换器或其它变量的集合(通常称库),必须使用多名字空间,其目的是防止同名冲突
创建库

下面是一个创建库的例子(假设保存在lib/my_test.ftl中):

<#macro copyright date>
 <p>Copyright (C) ${date} Julia Smith. All rights reserved.
 <br>Email: ${mail}</p>

</#macro> 

<#assign mail = "jsmith@acme.com">

使用import指令导入库到模板中,Freemarker会为导入的库创建新的名字空间,并可以通过import指令中指定的散列变量访问库中的变量:

<#import "/lib/my_test.ftl" as my>

<#assign mail="fred@acme.com">

<@my.copyright date="1999-2002"/>

${my.mail}

${mail} 

输出结果:
 <p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
 <br>Email: jsmith@acme.com</p>

jsmith@acme.com

fred@acme.com 

可以看到例子中使用的两个同名变量并没有冲突,因为它们位于不同的名字空间
可以使用assign指令在导入的名字空间中创建或替代变量,下面是一个例子:

<#import "/lib/my_test.ftl" as my>

${my.mail}

<#assign mail="jsmith@other.com" in my>

${my.mail} 
输出结果:

jsmith@acme.com

jsmith@other.com 
数据模型中的变量任何地方都可见,也包括不同的名字空间,下面是修改的库:

<#macro copyright date>
 <p>Copyright (C) ${date} ${user}. All rights reserved.</p>

</#macro>

<#assign mail = "${user}@acme.com">  
假设数据模型中的user变量的值是Fred,则下面的代码:

<#import "/lib/my_test.ftl" as my>

<@my.copyright date="1999-2002"/>

${my.mail}  
输出结果:
 <p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>

posted @ 2007-01-11 00:33 忆了又忆| 编辑 收藏

你想使用FreeMarker做的最后一件事是将表单域绑定到命令属性中。在第8章中,你使用JSP的 <spring:bind> 标签,而在第9.1.6节中,你是使用#springBind Velocity宏来实现这一点的。类似地,Spring提供了一组FreeMarker宏来进行这种绑定。

等价的FreeMarker宏是< @spring.bind>和<@spring.bindEscaped>。例如,程序清单9.4节选了 registerStudent.ftl中的一段,演示了如何使用<@spring.bind>指令将status信息绑定到表单中。

 程序清单9.4  在FreeMarker模板中使用<@spring.bind>
<@spring.bind "command.phone" />

  phone: <input type="text"

      name="${spring.status.expression}"

      value="${spring.status.value}">

  <font color="#FF0000">${spring.status.errorMessage}</font><br>

<@spring.bind "command.email" />

      email: <input type="text"

      name="${spring.status.expression}"

      value="${spring.status.value}">

  <font color="#FF0000">${spring.status.errorMessage}</font><br>

你可能已经注意到程序清单9.4和程序清单9.2非常相像。但这里有两个不同。首先,FreeMarker版中不是使用Velocity的#springBind宏,而是使用< @spring.bind>指令。其次,<@spring.bind>将状态信息绑定到${spring.status}而不是$ {status}。

正如Sping的Velocity宏,为了使用这些宏,必须设置FreeMarkerViewResolver的exposeMacroHelpers属性为true:

 

  <bean id="viewResolver" class="org.springframework.

          ➥web.servlet.view.freemarker.FreeMarkerViewResolver">

  …

    <property name="exposeSpringMacroHelpers">

      <value>true</value>

    </property>

  </bean>

 

最后,你还需要做一件事才能使用FreeMarker宏。在所有需要使用<@spring.bind>和<@spring.bindEscaped>的FreeMarker模板的顶部增加以下一行:

 

  <#import "/spring.ftl" as spring />

 

这一行会在模板中导入Spring的FreeMarker宏。

posted @ 2007-01-11 00:04 忆了又忆| 编辑 收藏

2007年1月10日

This is an attempt to put together a comprehensive reference for using Cascading Style Sheets with the Google Web Toolkit. I’ve assembled this document by first starting with the official documentation and then reviewing the source code. Where the source disagreed with the documentation I’ve sided with the source. I then did a runtime analysis of the sample applications that ship with the SDK to verify the results.

I feel there is a need for this because the documentation that comes with the SDK is rather unhelpful. The SDK says, basically, that widget styles are conventionally named [project]-[widget], e.g., gwt-Button, and style names correspond to CSS class names, so in order to style your button you would include something like

.gwt-Button { font-size: 150%; }
				

in your stylesheet. And that’s all it says. Really.

I believe this documentation to be inadequate for a number of reasons.

  1. The style is almost never as simple as the button example. Many GWT widgets correspond to messy nested tables, divs, and spans so it can be hard to know what it is you’re actually styling and what options are available to you in that context.
  2. The naming rule is not applied consistently within the SDK. The DialogBox class, for one, does not follow the rule.
  3. In some situations similarly named styles (*-selected*) are used in different or even contradictory manners making it hard to generalize your experience from one widget to another.
  4. In many cases the documentation is incorrect; the documented style simply is not implemented. The ListBox class should, according to both the general rule above and the specific class documentation, implement a style named gwt-ListBox. Nope, not there. Grep the source directory if you don’t believe me.

If I’ve left out a class below, it’s probably because it doesn’t participate in styling in any way. (I hope.) If you’re trying to read this straight through instead of just jumping to an item of interest, more power to you. If you find yourself yawning you might want to skip around a bit.

With that said…

AbsolutePanel

Implemented as a DIV and by default sets overflow to hidden. Contents are positioned absolutely according to given x, y values unless x == -1 and y == -1 in which case the widget is positioned statically.

<div style="overflow: hidden;"></div>
				

Button

Implemented as HTML BUTTON. Default style name is gwt-Button and is used. No default attributes. Can contain text or HTML.

<button class="gwt-Button" />
				

CellPanel

Implemented as a TABLE. No default styles. Can set border and cell-spacing attributes.

<table>
</table>
				

CheckBox

Implemented as HTML CHECKBOX. Default style name is gwt-CheckBox and is used. Automatically generates unique id of the form checkN where N is an integer. Uses checked, defaultChecked, and disabled attributes. No default styles.

<checkbox class="gwt-CheckBox" />
				

DeckPanel

Implemented using a DIV containing any number of children. The visibility of the individual children are controlled using the display attribute. The DeckPanel sets display to 'none' or '' as appropriate.

<div style="width: 100%; height: 100%"></div>
				

DialogBox

Default style names are gwt-DialogBox and Caption and are both used. Implemented as a DIV and the caption is also a DIV. (Technically, the caption is an HTML, which is a Label, which is implemented using a DIV.)

<div class="gwt-DialogBox">
  <table cell-spacing="0" cell-padding="0">
    <tbody>
      <tr>
      <td><div class="Caption">caption</div></td> </tr> <tr> <td> content </td> </tr> </tbody> </table> </div>

DockPanel

Implemented using TABLE. cell-spacing and cell-padding attributes both default to 0. Nesting of TR and TD elements can get quite complicated in order to achieve desired layout.

<table cell-spacing="0" cell-padding="0">
  <tbody>
  </tbody>
</table>
				

FlexTable

Just a TABLE, there’s nothing funky going on here.

<table>
  <tbody>
  </tbody>
</table>
				

FlowPanel

Implemented as a DIV with display set to inline.

<div style="display: inline;">content</div>
				

FocusPanel

A DIV. FocusPanel is only important in that it publishes a number of events (FOCUSEVENTS, KEYEVENTS, ONCLICK, and MOUSEEVENTS) and is very useful for containing widgets that don’t publish their own events. See Drag-and-Drop with the Google Web Toolkit.

<div>
  content
</div>
				

FocusWidget

Can be anything because it is implemented using whatever element is passed to it in the constructor. Interesting because it generates FOCUSEVENTS and KEYEVENTS.

Frame

Implemented as an IFRAME. Documented style name of gwt-Frame is not implemented.

<iframe>
</iframe>
				

Grid

Is just a table.

<table>
  <tbody>
  </tbody>
</table>
				

HTML

Implemented as a DIV with default style name of gwt-HTML. Can also set attribute white-space to normal or nowrap.

<div class="gwt-HTML">html</div>
				

HTMLPanel

Is a DIV that can either contain HTML exactly as HTMLor a collection of widgets. Does not use the gwt-HTML style. The most useful attribute of an HTMLPanel is that it contains the method createUniqueId that returns an id of the form HTMLPanel_N that can be used to apply styles to specific elements, as opposed to classes.

Contrast this with CheckBox which generates ids of the form checkN without either the capitalization or the underscore. Not a bug, just another minor inconsistency.

<div>
  content
</div>
				

HTMLTable

Unsurprisingly this class is implemented as a TABLE. The most important things to know about HTMLTable are (a) that it is the superclass for both FlexTable and Grid and (b) that it provides methods for setting the styles of individual rows or cells.

It is also worth noting that HTMLTable does not include a THEAD. The 0th row therefore must be used as something of a pseudo-header by applying any necessary styles.

<table>
  <tbody>
    <tr>Row 0 -- if you want a header you have to fake it here.</tr>
  </tbody>
</table>
				
// Style the first row to fake a header.
table.getRowFormatter(0).setStyleName("something-interesting");
				

HorizontalPanel

Implemented using a TABLE with all elements laid out as TDs in a single TR.

<table cell-spacing="0" cell-padding="0">
  <tbody>
    <tr>
      <td style="display: static; vertical-align: top;" align="left">Item 1</td>
      <td style="display: static; vertical-align: top;" align="left">Item 2</td>
    </tr>
  </tbody>
</table>
				

HyperLink

A DIV containing an anchor. Documented style name gwt-HyperLink is not implemented.

<div></div>
				

Image

Implemented as IMG. Documented style of gwt-Image is not implemented.

<img src="..." />
				

Label

Label is implemented as a DIV with a default style name of gwt-Label. Labels do not interpret their content as HTML and by default allow word-wrap. If you want to use HTML content in your label then you should use an instance of HTML. Both classes provide MOUSEEVENTS.

You can change the default word-wrap by calling the setWordWrap method.

<div class="gwt-Label">your text here</div>
				

ListBox

Implemented using SELECT with OPTION for elements. Documented style name gwt-ListBox is not implemented. Uses attributes selected, size, and multiple as part of implementation.

MenuBar

Implemented as a DIV containing a TABLE with menu items contained in TD elements. A horizontal MenuBar contains all menu items as children of a single TR and a vertical MenuBar uses a separate TR for each item. Simple enough. The documented style name of gwt-MenuBar is used and applied to the outer DIV.

<div class="gwt-MenuBar">
  <table>
    <tbody>
      
      <tr>
        <td class="gwt-MenuItem">text or html</td>
        <td class="gwt-MenuItem">text or html</td>
      </tr>
      <!-- example of a vertical menu
        <tr><td class="gwt-MenuItem">text or html</td></tr>
        <tr><td class="gwt-MenuItem">text or html</td></tr>
      -->
    </tbody>
  </table>
</div>
				

MenuItem

A MenuItem is a TD that can be inserted into a MenuBar. The default style name is gwt-MenuItem. A selected MenuItem has the additional style name of gwt-MenuItem-selected. I want to emphasize that the selected style is added to the default style, so that class="gwt-MenuItem" becomes class="gwt-MenuItem gwt-MenuItem-selected". This is not the case with all widgets and is another minor inconsistency in the GWT style design. See StackPanel for an example of the opposite behavior.

PasswordTextBox

Implemented as PASSWORD. Uses gwt-PasswordTextBox.

PopupPanel

Just a DIV.

RadioButton

Implemented as an instance of INPUT. Uses gwt-RadioButton.

RootPanel

A RootPanel can be attached to any element, but it will discard all children previously belonging to that element. If you stop to think about it, this can be useful in contexts outside of your application init.

ScrollPanel

A DIV with overflow set to scroll or auto. Defaults to auto.

<div style="overflow: auto;">
  content
</div>
				

SimplePanel

Just a DIV.

StackPanel

Implemented as a TABLE with 2 rows per item. In each pair of rows the first contains the caption and the second contains the corresponding widget. By default the TABLE is styled with gwt-StackPanel. The captions are styled with gwt-StackPanelItem and gwt-StackPanelItem-selected. When an item is selected the caption’s unselected style gwt-StackPanelItem is replaced with gwt-StackPanelItem-selected. Not all widgets behave this way. See MenuItem for an example of the opposite behavior.

<table class="gwt-StackPanel" cell-spacing="0" cell-padding="0">
  <tbody>
    
    <tr>
      <td class="gwt-StackPanelItem" height="1px">text/html</td>
    </tr>
    <tr>
      <td height="100%" valign="top">
        content -- a widget
      </td>
    </tr>
  </tbody>
</table>
				

TabBar

A TabBar is implemented using a HorizontalPanel so it is effectively a TABLE. The style name gwt-TabBar applies to the TABLE — that is, to the actual tabs. The style gwt-TabBarFirst applies to the first (effectively empty) HTML widget and is only useful for creating some sort of left border effect. The style gwt-TabBarRest applies to that part of the TabBar following the tabs.

When a tab is selected the style gwt-TabBarItem-selected gets added to the existing style. This behavior is like that of MenuItem but opposite that of StackPanel.

<table class="gwt-TabBar" cell-spacing="0" cell-padding="0">
  <tbody>
    <tr>
      <td class="gwt-TabBarFirst" style="height: 100%;"><div class="gwt-HTML" style="height: 100%;">&amp;nbsp;</div></td>
      <td>Tab #1</td>
      <td>Tab #2</td>
      <td class="gwt-TabBarRest" style="width: 100%;"><div class="gwt-HTML" style="height: 100%;">&amp;nbsp;</div></td>
    </tr>
  </tbody>
</table>
				

TabPanel

Implemented as a VerticalPanel containing a TabBar and a DeckPanel. In other words, it’s a bunch of nested tables. The style gwt-TabPanel applies to the top-level TABLE, gwt-TabBar still applies to the contained TABLE implementing the TabBar, and gwt-TabPanelBottom styles the DIV containing the actual content.

Note that the TabBar gets the added default style of width: 100%. This ensures that the TabBar is as wide as the content panel and is the reason the gwt-TabBarRest style is important. It’s all about how you want that empty space to look.

<table class="gwt-TabPanel" cell-spacing="0" cell-padding="0">
  <tbody>
    <tr>
      <td>
        
        <table class="gwt-TabBar" style="width: 100%;" cell-spacing="0" cell-padding="0">
          <tbody>
            <tr>
              <td class="gwt-TabBarFirst" style="height: 100%;"><div class="gwt-HTML" style="height: 100%;">&amp;nbsp;</div></td>
	      
              <td class="gwt-TabBarRest" style="width: 100%;"><div class="gwt-HTML" style="height: 100%;">&amp;nbsp;</div></td>
            </tr>
          </tbody>
        </table>
      </td>
    </tr>
    <tr>
      <td>
        <div class="gwt-TabPanelBottom">
	  
	</div>
      </td>
    </tr>
  </tbody>
</table>
				

TextArea

Implemented as a TEXTAREA with a default style of gwt-TextArea.

TextBox

<input type="text" class="gwt-TextBox" />
				

Tree

Implemented as a DIV containing nested TreeItems. The style name gwt-Tree applies to the DIV and the style overflow defaults to auto.

<div class="gwt-Tree" style="overflow: auto;">
  
  <div style="position: relative; margin-left: 16;" (handle)>
    <table>
      <tr>
        <td></td>
        <td></td>
      </tr>
    </table>
    
  </div>
</div>
				

TreeItem

Implemented as a TABLE nested within a DIV. The styles gwt-TreeItem and gwt-TreeItem-selected apply to the nested content element, a SPAN. The selected state gwt-TreeItem-selectedreplaces the unselected state gwt-TreeItem, like StackPanel but unlike MenuItem or TabBar.

<div style="position: relative; margin-left: 16; white-space: nowrap" (handle)>
  <table style="white-space: nowrap;">
    <tr>
      <td style="vertical-align: middle;"><img src="tree_white.gif" /></td>
      <td style="vertical-align: middle;">content</td>
    </tr>
  </table>
  children
</div>
				

VerticalPanel

Implemented using a TABLE with all elements laid out as TRs.

<table cell-spacing="0" cell-padding="0">
  <tbody>
    <tr><td style="display: static; vertical-align: top;" align="left">Item 1</td></tr>
    <tr><td style="display: static; vertical-align: top;" align="left">Item 2</td></tr>
  </tbody>
</table>
				

posted @ 2007-01-10 17:08 忆了又忆| 编辑 收藏

2006年12月15日

文本编辑器是所有计算机系统中最常用的一种工具。UNIX下的编辑器有ex,sed和vi等,其中,使用最为广泛的是vi,而vi命令繁多,论坛里好像这方面的总结不多,以下稍做总结,以资共享!渴望更正和补充! 

进入vi的命令
vi filename :打开或新建文件,并将光标置于第一行首
vi +n filename :打开文件,并将光标置于第n行首
vi + filename :打开文件,并将光标置于最后一行首
vi +/pattern filename:打开文件,并将光标置于第一个与pattern匹配的串处
vi -r filename :在上次正用vi编辑时发生系统崩溃,恢复filename
vi filename....filename :打开多个文件,依次进行编辑

移动光标类命令
h :光标左移一个字符
l :光标右移一个字符
space:光标右移一个字符
Backspace:光标左移一个字符
k或Ctrl+p:光标上移一行
j或Ctrl+n :光标下移一行
Enter :光标下移一行
w或W :光标右移一个字至字首
b或B :光标左移一个字至字首
e或E :光标右移一个字至字尾
) :光标移至句尾
( :光标移至句首
}:光标移至段落开头
{:光标移至段落结尾
nG:光标移至第n行首
n+:光标下移n行
n-:光标上移n行
n$:光标移至第n行尾
H :光标移至屏幕顶行
M :光标移至屏幕中间行
L :光标移至屏幕最后行
0:(注意是数字零)光标移至当前行首
$:光标移至当前行尾

屏幕翻滚类命令
Ctrl+u:向文件首翻半屏
Ctrl+d:向文件尾翻半屏
Ctrl+f:向文件尾翻一屏
Ctrl+b;向文件首翻一屏
nz:将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部。

插入文本类命令
i :在光标前
I :在当前行首
a:光标后
A:在当前行尾
o:在当前行之下新开一行
O:在当前行之上新开一行
r:替换当前字符
R:替换当前字符及其后的字符,直至按ESC键
s:从当前光标位置处开始,以输入的文本替代指定数目的字符
S:删除指定数目的行,并以所输入文本代替之
ncw或nCW:修改指定数目的字
nCC:修改指定数目的行

删除命令
ndw或ndW:删除光标处开始及其后的n-1个字
do:删至行首
d$:删至行尾
ndd:删除当前行及其后n-1行
x或X:删除一个字符,x删除光标后的,而X删除光标前的
Ctrl+u:删除输入方式下所输入的文本

搜索及替换命令
/pattern:从光标开始处向文件尾搜索pattern
?pattern:从光标开始处向文件首搜索pattern
n:在同一方向重复上一次搜索命令
N:在反方向上重复上一次搜索命令
:s/p1/p2/g:将当前行中所有p1均用p2替代
:n1,n2s/p1/p2/g:将第n1至n2行中所有p1均用p2替代
:g/p1/s//p2/g:将文件中所有p1均用p2替换

选项设置
all:列出所有选项设置情况
term:设置终端类型
ignorance:在搜索中忽略大小写
list:显示制表位(Ctrl+I)和行尾标志($)
number:显示行号
report:显示由面向行的命令修改过的数目
terse:显示简短的警告信息
warn:在转到别的文件时若没保存当前文件则显示NO write信息
nomagic:允许在搜索模式中,使用前面不带“\”的特殊字符
nowrapscan:禁止vi在搜索到达文件两端时,又从另一端开始
mesg:允许vi显示其他用户用write写到自己终端上的信息

最后行方式命令
:n1,n2 co n3:将n1行到n2行之间的内容拷贝到第n3行下
:n1,n2 m n3:将n1行到n2行之间的内容移至到第n3行下
:n1,n2 d :将n1行到n2行之间的内容删除
:w :保存当前文件
:e filename:打开文件filename进行编辑
:x:保存当前文件并退出
:q:退出vi
:q!:不保存文件并退出vi
:!command:执行shell命令command
:n1,n2 w!command:将文件中n1行至n2行的内容作为command的输入并执行之,若不指定n1,n2,则表示将整个文件内容作为command的输入
:r!command:将命令command的输出结果放到当前行

寄存器操作
"?nyy:将当前行及其下n行的内容保存到寄存器?中,其中?为一个字母,n为一个数字
"?nyw:将当前行及其下n个字保存到寄存器?中,其中?为一个字母,n为一个数字
"?nyl:将当前行及其下n个字符保存到寄存器?中,其中?为一个字母,n为一个数字
"?p:取出寄存器?中的内容并将其放到光标位置处。这里?可以是一个字母,也可以是一个数字
ndd:将当前行及其下共n行文本删除,并将所删内容放到1号删除寄存器中。

posted @ 2006-12-15 18:30 忆了又忆| 编辑 收藏

ls 命令可以说是Linux下最常用的命令之一。它有众多的选项,其中有很多是很有用的,你是否熟悉呢?下面列出了 ls 命令的绝大多数选项。


-a 列出目录下的所有文件,包括以 . 开头的隐含文件。
-b 把文件名中不可输出的字符用反斜杠加字符编号(就象在C语言里一样)的形式列出。
-c 输出文件的 i 节点的修改时间,并以此排序。
-d 将目录象文件一样显示,而不是显示其下的文件。
-e 输出时间的全部信息,而不是输出简略信息。
-f -U 对输出的文件不排序。
-g 无用。
-i 输出文件的 i 节点的索引信息。
-k 以 k 字节的形式表示文件的大小。
-l 列出文件的详细信息。
-m 横向输出文件名,并以“,”作分格符。
-n 用数字的 UID,GID 代替名称。
-o 显示文件的除组信息外的详细信息。
-p -F 在每个文件名后附上一个字符以说明该文件的类型,“*”表示可执行的普通
文件;“/”表示目录;“@”表示符号链接;“|”表示FIFOs;“=”表示套
接字(sockets)。
-q 用?代替不可输出的字符。
-r 对目录反向排序。
-s 在每个文件名后输出该文件的大小。
-t 以时间排序。
-u 以文件上次被访问的时间排序。
-x 按列输出,横向排序。
-A 显示除 “.”和“..”外的所有文件。
-B 不输出以 “~”结尾的备份文件。
-C 按列输出,纵向排序。
-G 输出文件的组的信息。
-L 列出链接文件名而不是链接到的文件。
-N 不限制文件长度。
-Q 把输出的文件名用双引号括起来。
-R 列出所有子目录下的文件。
-S 以文件大小排序。
-X 以文件的扩展名(最后一个 . 后的字符)排序。
-1 一行只输出一个文件。
--color=no 不显示彩色文件名
--help 在标准输出上显示帮助信息。
--version 在标准输出上输出版本信息并退出。

posted @ 2006-12-15 18:29 忆了又忆| 编辑 收藏

  VSFTPD是一种在UNIX/Linux中非常安全且快速的FTP服务器,目前已经被许多大型站点所采用。VSFTPD支持将用户名和口令保存在数据库文件或数据库服务器中。VSFTPD称这种形式的用户为虚拟用户。相对于FTP的本地(系统)用户来说,虚拟用户只是FTP服务器的专有用户,虚拟用户只能访问FTP服务器所提供的资源,这大大增强系统本身的安全性。相对于匿名用户而言,虚拟用户需要用户名和密码才能获取FTP服务器中的文件,增加了对用户和下载的可管理性。对于需要提供下载服务,但又不希望所有人都可以匿名下载;既需要对下载用户进行管理,又考虑到主机安全和管理方便的FTP站点来说,虚拟用户是一种极好的解决方案。本文介绍在RedHat Linux 9上如何将VSFTPD的虚拟用户名和密码保存在MySQL数据库服务器中。

  一、VSFTPD的安装

  目前,VSFTPD的最新版本是1.2.0版。官方下载地址为ftp://vsftpd.beasts.org/users/cevans/vsftpd-1.2.0.tar.gz。在安装前,需要先做以下准备工作:

  VSFTPD默认配置中需要“nobody”用户。在系统中添加此用户,如果用户已经存在,useradd命令有相应提示。
  [root@hpe45 root]# useradd nobody
  useradd: user nobody exists

  VSFTPD默认配置中需要“/usr/share/empty”目录。在系统中此目录,如果目录已经存在,mkdir命令有相应提示。
  [root@hpe45 root]# mkdir /usr/share/empty/
  mkdir: cannot create directory '/usr/share/empty': File exists

  VSFTPD提供匿名FTP服务时,需要“ftp”用户和一个有效的匿名目录。
  [root@hpe45 root]# mkdir /var/ftp/
  [root@hpe45 root]# useradd -d /var/ftp ftp
  接下来的操作对于ftp用户是否已经存在都是有用的。
  [root@hpe45 root]# chown root.root /var/ftp
  [root@hpe45 root]# chmod og-w /var/ftp

  以上准备工作完成后,我们就可以开始编译源代码了。假定我们下载的vsftpd-1.2.0.tar.gz在/root目录,执行以下命令:
  [root@hpe45 root]# tar zxvf vsftpd-1.2.0.tar.gz
  [root@hpe45 root]# cd vsftpd-1.2.0
  [root@hpe45 vsftpd-1.2.0]# make
  [root@hpe45 vsftpd-1.2.0]# make install

  上面的“make install”命令将编译好的二进制文件、手册等复制到相应目录。在RHL9上,可能需要手动执行以下复制:
  [root@hpe45 vsftpd-1.2.0]# cp vsftpd /usr/local/sbin/vsftpd
  [root@hpe45 vsftpd-1.2.0]# cp vsftpd.conf.5 /usr/local/share/man/man5
  [root@hpe45 vsftpd-1.2.0]# cp vsftpd.8 /usr/local/share/man/man8

  接下来,我们复制一个简单的配置文件作为基础供后面修改。
  [root@hpe45 vsftpd-1.2.0]# cp vsftpd.conf /etc
  [root@hpe45 vsftpd-1.2.0]# cp RedHat/vsftpd.pam /etc/pam.d/ftp
  复制PAM验证文件,以允许本地用户登录VSFTPD。
  [root@hpe45 vsftpd-1.2.0]# cp RedHat/vsftpd.pam /etc/pam.d/ftp

  二、创建guest用户

  VSFTPD采用PAM方式验证虚拟用户。由于虚拟用户的用户名/口令被单独保存,因此在验证时,VSFTPD需要用一个系统用户的身份来读取数据库文件或数据库服务器以完成验证,这就是VSFTPD的guest用户。这正如同匿名用户也需要有一个系统用户ftp一样。当然,我们也可以把guest用户看成是虚拟用户在系统中的代表。下面在系统中添加vsftpdguest用户,作为VSFTPD的guest。
  [root@hpe45 vsftpd-1.2.0]# useradd vsftpdguest
  当虚拟用户登录后,所在的位置为vsftpdguest的自家目录/home/vsftpdguest。如果要让虚拟用户登录到/var/ftp等其他目录,修改vsftpdguest的自家目录即可。

  三、设置VSFTPD配置文件

  在/etc/vsftpd.conf文件中,加入以下选项:
  guest_enable=YES
  guest_username=vsftpdguest

  然后执行以下命令,让VSFTPD在后台运行:
  [root@hpe45 vsftpd-1.2.0]# /usr/local/sbin/vsftpd &

  四、将虚拟用户保存在MySQL数据库服务器中

  我们建立数据库vsftpdvu,表users,字段name和passwd用于保存虚拟用户的用户名和口令,同时增加两个虚拟用户xiaotong和xiaowang。

  [root@hpe45 vsftpd-1.2.0]# mysql -p
  mysql>create database vsftpdvu;
  mysql>use vsftpdvu;
  mysql>create table users(name char(16) binary,passwd char(16) binary);
  mysql>insert into users (name,passwd) values ('xiaotong',password('qqmywife'));
  mysql>insert into users (name,passwd) values ('xiaowang',password('ttmywife'));
  mysql>quit

  然后,授权vsftpdguest可以读vsftpdvu数据库的users表。执行以下命令:
  [root@hpe45 vsftpd-1.2.0]# mysql -u root mysql -p
  mysql>grant select on vsftpdvu.users to vsftpdguest@localhost identified by 'i52serial0';
  mysql>quit

  如果要验证刚才的操作是否成功可以执行下面命令:
  [root@hpe45 vsftpd]#mysql -u vsftpdguest -pi52serial0 vsftpdvu
  mysql>select * from users;
  如果成功,将会列出xiaotong、xiaowang和加密后的密码

  五、设置MySQL的PAM验证

  这里我们要用到一个利用mysql进行pam验证的开源项目(http://sourceforge.net/projects/pam-mysql/)。首先从网站下载它的程序包pam_myql-0.5.tar.gz,复制到/root目录中。在编译安装之前,要确保mysql-devel的RPM包已经安装在你的机器上,如果没有请从RHL安装光盘中安装该包。然后,执行以下命令:
  [root@hpe45 root]#tar xvzf pam_mysql-0.5.tar.gz
  [root@hpe45 root]#cd pam_mysql
  [root@hpe45 pam_mysql]#make
  [root@hpe45 pam_mysql]#make install
  make install这一步可能会出现错误,那只好手动将该目录下生成的pam_mysql.o复制到/lib/security目录下。
  接下来,我们要设置vsftpd的PAM验证文件。打开/etc/pam.d/ftp文件,加入以下内容:
  auth required pam_mysql.o user=vsftpdguest passwd=i52serial0 host=localhost db=vsftpdvu table=users usercolumn=name passwdcolumn=passwd crypt=2
  account required pam_mysql.o user=vsftpdguest passwd=i52serial0 host=localhost db=vsftpdvu table=users usercolumn=name passwdcolumn=passwd crypt=2
  上面涉及到的参数,只要对应前面数据库的设置就可以明白它们的含义。这里需要说明的是crypt参数。crypt表示口令字段中口令的加密方式:crypt=0,口令以明文方式(不加密)保存在数据库中;crypt=1,口令使用UNIX系统的DES加密方式加密后保存在数据库中;crypt=2,口令经过MySQL的password()函数加密后保存。

  六、进一步的虚拟用户设置

  经过以上的步骤,虚拟用户就可以正常使用了。这里介绍进一步的虚拟用户设置。首先,介绍虚拟用户的权限设置。

  VSFTPD-1.2.0新添了virtual_use_local_privs参数,当该参数激活(YES)时,虚拟用户使用与本地用户相同的权限。当此参数关闭(NO)时,虚拟用户使用与匿名用户相同的权限,这也就是VSFTPD-1.2.0之前版本对虚拟用户权限的处理方法。这两者种做法相比,后者更加严格一些,特别是在有写访问的情形下。默认情况下此参数是关闭的(NO)。
  当virtual_use_local_privs=YES时,只需设置write_enable=YES,虚拟用户就可以就拥有写权限。而virtual_use_local_privs=NO时,对虚拟用户权限的设置就更多一些更严格一些。
  控制虚拟用户浏览目录:如果让用户不能浏览目录,但仍可以对文件操作,那么需要执行以下二个步骤:一,配置文件中,anon_world_readable_only=YES。二,虚拟用户目录的权限改为只能由vsftpdguest操作:
  [root@hpe45 root]# chown vsftpdguest.vsftpdguest /home/vsftpdguest
  [root@hpe45 root]# chmod 700 /home/vsftpdguest
  允许虚拟用户上传文件:
  write_enable=YES
  anon_upload_enable=YES
  允许虚拟用户修改文件名和删除文件:
  anon_other_write_enable=YES
  由于以上选项的设置同样会对匿名用户生效。如果不想匿名用户趁机拥有同样的权限,最好是禁止匿名用户登录。

  其次,由于虚拟用户在系统中是vsftpdguest身份,所以可以访问到系统的其他目录。为了更加安全,我们可以将虚拟用户限制在自家目录下。有两种做法:一,在配置文件中增加以下选项
  chroot_local_user=NO
  chroot_list_enable=YES
  chroot_list_file=/etc/vsftpd.chroot_list
  然后,在/etc/vsftpd.chroot_list文件中加入虚拟用户名xiaotong和xiaowang。
  第二种做法,在配置文件中修改chroot_local_user=YES。
  经过修改后,虚拟用户登录后其根目录就限制在/home/vsftpdguest下,无法访问其他目录。

  七、虚拟用户的个人目录

  大家可以发现,无论是哪个虚拟用户,登录后所在的目录都是/home/vsftpdguest,即都是guest_username用户的自家目录。下面,介绍如何为每个虚拟用户建立自家目录。首先,在主配置文件中加入以下选项:
  user_config_dir=/etc/vsftpd/vsftpd_user_conf
  然后,生成/etc/vsftpd/vsftpd_user_conf目录,并在该目录下建立与特定虚拟用户同名的文件:
  [root@hpe45 root]# mkdir /etc/vsftpd/vsftpd_user_conf
  [root@hpe45 root]# cd /etc/vsftpd/vsftpd_user_conf
  [root@hpe45 vsftpd_user_conf]# touch xiaowang
  以上的操作为虚拟用户xiaowang建立了个人配置文件/etc/vsftpd/vsftpd_user_conf/xiaowang。接下来,在xiaowang的个人配置文件中将xiaowang的自家目录修改为/home/xiaowang,配置选项为:
  local_root=/home/xiaowang
  然后,新建xiaowang目录,并将权限设为vsftpdguest:
  [root@hpe45 vsftpd_user_conf]# mkdir /home/xiaowang
  [root@hpe45 vsftpd_user_conf]# chown vsftpdguest.vsftpdguest ./xiaowang
  [root@hpe45 vsftpd_user_conf]# chmod 600 /home/xiaowang
  经过以上设置,xiaowang登录VSFTPD后,用“pwd”指令就可以发现被自己被定位到自己的“/home/xiaowang”目录。
  从文件系统层次来看,由于“/home/xiaowang”目录的权限是属于vsftpdguest的,所以其他的虚拟用户同样也可以访问xiaowang的自家目录。解决这个问题也很简单,我们只需要让VSFTPD负责将虚拟用户限制在其自家目录,就可以避免虚拟用户的互相访问。具体做法参照前面第六步中所述,这里不再赘述。经过以上设置后,虚拟用户就可以拥有属于自己的目录了。

posted @ 2006-12-15 18:27 忆了又忆| 编辑 收藏

    在linux下一些常用的关机/重启命令有shutdown、halt、reboot、及init,它们都可以达到重启系统的目的,但每个命令的内部工作过程是不同的,通过本文的介绍,希望你可以更加灵活的运用各种关机命令。

1.shutdown
   shutdown命令安全地将系统关机。 有些用户会使用直接断掉电源的方式来关闭linux,这是十分危险的。因为linux与windows不同,其后台运行着许多进程,所以强制关机可能会导致进程的数据丢失﹐使系统处于不稳定的状态﹐甚至在有的系统中会损坏硬件设备。 

    而在系统关机前使用shutdown命令﹐系统管理员会通知所有登录的用户系统将要关闭。并且login指令会被冻结﹐即新的用户不能再登录。直接关机或者延迟一定的时间才关机都是可能的﹐还可能重启。这是由所有进程〔process〕都会收到系统所送达的信号〔signal〕决定的。这让像vi之类的程序有时间储存目前正在编辑的文档﹐而像处理邮件〔mail〕和新闻〔news〕的程序则可以正常地离开等等。

    shutdown执行它的工作是送信号〔signal〕给init程序﹐要求它改变runlevel。Runlevel 0被用来停机〔halt〕﹐runlevel 6是用来重新激活〔reboot〕系统﹐而runlevel 1则是被用来让系统进入管理工作可以进行的状态﹔这是预设的﹐假定没有-h也没有-r参数给shutdown。要想了解在停机〔halt〕或者重新开机〔reboot〕过程中做了哪些动作﹐你可以在这个文件/etc/inittab里看到这些runlevels相关的资料。
   shutdown 参数说明:
   [-t] 在改变到其它runlevel之前﹐告诉init多久以后关机。
   [-r] 重启计算器。
   [-k] 并不真正关机﹐只是送警告信号给每位登录者〔login〕。
   [-h] 关机后关闭电源〔halt〕。
   [-n] 不用init﹐而是自己来关机。不鼓励使用这个选项﹐而且该选项所产生的后果往往不总是你所预期得到的。
   [-c] cancel current process取消目前正在执行的关机程序。所以这个选项当然没有时间参数﹐但是可以输入一个用来解释的讯息﹐而这信息将会送到每位使用者。
   [-f] 在重启计算器〔reboot〕时忽略fsck。 
       [-F] 在重启计算器〔reboot〕时强迫fsck。
   [-time] 设定关机〔shutdown〕前的时间。
     
2.halt----最简单的关机命令
   其实halt就是调用shutdown -h。halt执行时﹐杀死应用进程﹐执行sync系统调用﹐文件系统写操作完成后就会停止内核。
   参数说明:
   [-n] 防止sync系统调用﹐它用在用fsck修补根分区之后﹐以阻止内核用老版本的超级块〔superblock〕覆盖修补过的超级块。
   [-w] 并不是真正的重启或关机﹐只是写wtmp〔/var/log/wtmp〕纪录。
   [-d] 不写wtmp纪录〔已包含在选项[-n]中〕。
   [-f] 没有调用shutdown而强制关机或重启。
   [-i] 关机〔或重启〕前﹐关掉所有的网络接口。
   [-p] 该选项为缺省选项。就是关机时调用poweroff。
   
3.reboot
    reboot的工作过程差不多跟halt一样﹐不过它是引发主机重启﹐而halt是关机。它的参数与halt相差不多。

4.init
   init是所有进程的祖先﹐它的进程号始终为1﹐所以发送TERM信号给init会终止所有的用户进程﹑守护进程等。shutdown 就是使用这种机制。init定义了8个运行级别(runlevel), init 0为关机﹐init 1为重启。关于init可以长篇大论﹐这里就不再叙述。另外还有 telinit命令可以改变init的运行级别﹐比如﹐telinit -iS可使系统进入单用户模式﹐并且得不到使用shutdown时的信息和等待时间。

posted @ 2006-12-15 18:25 忆了又忆| 编辑 收藏

.tar
解包: tar xvf FileName.tar
打包:tar cvf FileName.tar DirName
(注:tar是打包,不是压缩!)
---------------------------------------------
.gz
解压1:gunzip FileName.gz
解压2:gzip -d FileName.gz
压缩:gzip FileName
.tar.gz
解压:tar zxvf FileName.tar.gz
压缩:tar zcvf FileName.tar.gz DirName
---------------------------------------------
.bz2
解压1:bzip2 -d FileName.bz2
解压2:bunzip2 FileName.bz2
压缩: bzip2 -z FileName
.tar.bz2
解压:tar jxvf FileName.tar.bz2
压缩:tar jcvf FileName.tar.bz2 DirName
---------------------------------------------
.bz
解压1:bzip2 -d FileName.bz
解压2:bunzip2 FileName.bz
压缩:未知
.tar.bz
解压:tar jxvf FileName.tar.bz
压缩:未知
---------------------------------------------
.Z
解压:uncompress FileName.Z
压缩:compress FileName
.tar.Z
解压:tar Zxvf FileName.tar.Z
压缩:tar Zcvf FileName.tar.Z DirName
---------------------------------------------
.tgz
解压:tar zxvf FileName.tgz
压缩:未知
.tar.tgz
解压:tar zxvf FileName.tar.tgz
压缩:tar zcvf FileName.tar.tgz FileName
---------------------------------------------
.zip
解压:unzip FileName.zip
压缩:zip FileName.zip DirName
---------------------------------------------
.rar
解压:rar a FileName.rar
压缩:r ar e FileName.rar


rar请到:http://www.rarsoft.com/download.htm 下载!
解压后请将rar_static拷贝到/usr/bin目录(其他由$PATH环境变量指定的目录也可以):
[root@www2 tmp]# cp rar_static /usr/bin/rar
---------------------------------------------
.lha
解压:lha -e FileName.lha
压缩:lha -a FileName.lha FileName

lha请到:http://www.infor.kanazawa-it.ac.jp/.../lhaunix/下载!
>解压后请将lha拷贝到/usr/bin目录(其他由$PATH环境变量指定的目录也可以):
[root@www2 tmp]# cp lha /usr/bin/
---------------------------------------------
.rpm
解包:rpm2cpio FileName.rpm | cpio -div
---------------------------------------------
.tar .tgz .tar.gz .tar.Z .tar.bz .tar.bz2 .zip .cpio .rpm .deb .slp .arj .rar .ace .lha .lzh 
.lzx .lzs .arc .sda .sfx .lnx .zoo .cab .kar .cpt .pit .sit .sea
解压:sEx x FileName.*
压缩:sEx a FileName.* FileName

sEx只是调用相关程序,本身并无压缩、解压功能,请注意!
sEx请到: http://sourceforge.net/projects/sex下载!
解压后请将sEx拷贝到/usr/bin目录(其他由$PATH环境变量指定的目录也可以):
[root@www2 tmp]# cp sEx /usr/bin/


参考文献:Linux 文件压缩工具指南
(其实看帮助是最好的方法,一般各个命令都可以用“--help”参数得到常用使用方法!)
发布人:会游泳的鱼 来自:LinuxByte

posted @ 2006-12-15 18:23 忆了又忆| 编辑 收藏

wget常用参数如下

  GNY Wget ,一个非交谈式的网路抓档工具.

  用法: wget [选项]... [URL]...

  命令的引数使用长项目与短项目相同.

  启动:

  -V, --version显示Wget的版本并且离开.

  -h, --help显示这个说明档.

  -b, -background在启动之後跳到背景去.

  -e, -execute=COMMAND执行一个`.wgetrc'里面的COMMAND指令.

  纪录档与输入的档案:

  -o, --output-file=FILE纪录讯息到FILE去.

  -a, -append-output=FILE增加讯息到FILE去.

  -d, --debug显示除错的输出.

  -q, --quiet安静模式(不输入任何讯息).

  -v, --verbose冗长模式(这是内定值).

  -nv, --non-verbose关闭verboseness,但不是安静模式.

  -i, --input-file=FILE从FILE读取URL .

  -F, --force-html把输入的档案当作HTML.

  下载:

  -t, --tries=NUMBER设定重复尝试NUMBER次(0是无限制).

  -O --output-document=FILE把文件写到FILE里.

  -nc, --no-clobber不破坏已经存在的档案.

  -c, --continue重新取得一个已经存在的档案.

  --dot-style=STYLE设定取回状况的显示风格.

  -N, --timestamping不取回比本地旧的档案.

  -S, --server-response显示伺服器回应状况.

  --spider不下载任何东西.

  -T, --timeout=SECONDS设定读取时超过的时间为SECONDS秒.

  -w, --wait=SECONDS在取回档案时等待SECONDS秒.

  -Y, --proxy=on/off开启或关闭Proxy.

  -Q, --quota=NUMBER设定取回档案的定额限制为NUMBER个.

  目录:

  -nd --no-directories不建立目录.

  -x, --force-directories强制进行目录建立的工作.

  -nH, --no-host-directories不建立主机的目录.

  -P, --directory-prefix=PREFIX把档案存到PREFIX/...

  --cut-dirs=NUMBER忽略NUMBER个远端的目录元件.

  HTTP选项:

  --http-user=USER设http使用者为USER.

  --http0passwd=PASS设http使用者的密码为PASS.

  -C, --cache=on/off提供/关闭快取伺服器资料(正常情况为提供).

  --ignore-length忽略`Content-Length'标头栏位.

  --proxy-user=USER设USER为Proxy使用者名称.

  --proxy-passwd=PASS设PASS为Proxy密码.

  -s, --save-headers储存HTTP标头成为档案.

  -U, --user-agent=AGENT使用AGENT取代Wget/VERSION作为识别代号.

  FTP选项:

  --retr-symlinks取回FTP的象徵连结.

  -g, --glob=on/off turn file name globbing on ot off.

  --passive-ftp使用"passive"传输模式.

  使用递回方式的取回:

  -r, --recursive像是吸入web的取回--请小心使用!.

  -l, --level=NUMBER递回层次的最大值(0不限制).

  --delete-after删除下载完毕的档案.

  -k, --convert-links改变没有关连的连结成为有关连.

  -m, --mirror开启适合用来映射的选项.

  -nr, --dont-remove-listing不要移除`.listing'档.

  递回式作业的允许与拒绝选项:

  -A, --accept=LIST允许的扩充项目的列表.

  -R, --reject=LIST拒绝的扩充项目的列表.

  -D, --domains=LIST允许的网域列表.

  --exclude-domains=LIST拒绝的网域列表(使用逗号来分隔).

  -L, --relative只跟随关联连结前进.

  --follow-ftp跟随HTML文件里面的FTP连结.

  -H, --span-hosts当开始递回时便到外面的主机.

  -I, --include-directories=LIST允许的目录列表.

  -X, --exclude-directories=LIST排除的目录列表.

  -nh, --no-host-lookup不透过DNS查寻主机.

  -np, --no-parent不追朔到起源目录.

  范例一:mirror一个网站

  wget -r www.redhat.com

  范例二:mirror一个网站下的某个目录:

  wget -r www.redhat.com/mirrors/LDP

  范例三:结合nohup在后台运行,让机器自动下载,并生成nohup.out文件,纪录下载过程的速度。
  nohup wget -c -t0 -T120 -i list.txt &

posted @ 2006-12-15 18:17 忆了又忆| 编辑 收藏

2006年12月5日

扑克发牌算法是棋牌游戏中常用的基础算法,也是游戏开发人员需要熟悉的基础算法之一。下面介绍一下该算法的一种实现方式。
首先给扑克牌中每张牌设定一个编号,下面算法实现的编号规则如下:
1.红桃按照从小到大依次为: 1-13
2.方块按照从小到大依次为: 14-26
3.黑桃按照从小到大依次为: 27-39
4.梅花按照从小到大依次为: 40-52
5.小王为 53 ,大王为 54

算法实现如下:
1.首先按照以上编号规则初始化一个包含 108 个数字的数组
2.每次随机从该数组中抽取一个数字,分配给保存玩家数据的数组

实现该功能的代码如下所示:

import java.util.*;

/**
 * 发牌算法的实现
 * 要求:把 2 副牌,也就是 108 张,发给 4 个人,留 6 张底牌
 */

public class Exec {
    public static void main(String[] args) {
        // 存储 108 张牌的数组
        int[] total = new int[108];
        // 存储四个玩家的牌
        int[][] player = new int[4][25];
        // 存储当前剩余牌的数量
        int leftNum = 108;
        // 随机数字
        int ranNumber;
        // 随机对象
        Random random = new Random();
        // 初始化数组
        for (int i = 0; i < total.length; i++) {
            total[i] = (i + 1) % 54;
            // 处理大小王编号
            if (total[i] == 0) {
                total[i] = 54;
            }
        }
        // 循环发牌
        for (int i = 0; i < 25; i++) {
            // 为每个人发牌
            for (int j = 0; j < player.length; j++) {
                // 生成随机下标
                ranNumber = random.nextInt(leftNum);
                // 发牌
                player[j][i] = total[ranNumber];
                // 移动已经发过的牌
                total[ranNumber] = total[leftNum - 1];
                // 可发牌的数量减少 1
                leftNum--;
            }
        }
        // 循环输出玩家手中的牌
        for (int i = 0; i < player.length; i++) {
            for (int j = 0; j < player[i].length; j++) {
                System.out.print("  " + player[i][j]);
            }
            System.out.println();
        }
        // 底牌
        for (int i = 0; i < 8; i++) {
            System.out.print("  " + total[i]);
        }
        System.out.println();
    }
}

posted @ 2006-12-05 22:36 忆了又忆| 编辑 收藏