报表在信息系统中占据了极为重要的位置,广义上讲,报表主要以多样的格式呈现和打印动态数据,动态数据主要指保存在数据库中的数据,也可以是文本中的数据、XML数据、Hibernate数据、EJB数据、自定义数据或则是其他程序产生的数据等,报表最显著的特征是它能够高度灵活处理数据,帮助用户全面的利用分散的数据。
在JSP中,通过JavaScript脚本调用IE浏览器自身的打印功能可以将当前页面中的内容进行打印。调用JavaScript脚本window对象的print()方法,可以实现打印功能。
语法:
window.print();
例如,在超链接标记中的onClick事件调用Window对象的print()方法,代码如下:
CSS在网页布局中的应用得到不断的扩展,例如,在XHTML网站设计标准中,可以采用DIV+CSS的方式实现各种定位等。其优点如下:
(1)它本身符合W3C标准,并且支持浏览器的向后兼容。
(2)使用CSS技术设计的网页,能够更容易被搜索引擎收录。
(3)CSS设计方法能够将页面显示内容和外观样式分离开来、能够通过CSS样式对DIV标记进行格式限定,从而减少程序代码的编写数量,并且能缩短网页打开时间以方便用户浏览。内容和样式的分离,使页面和样式的调整变得更加方便。
通过JavaScript+CSS设计方法可以高效的制作出符合用户要求的页面或者报表格式等。
在JSP页面中可以调用IE内置的浏览器控件WebBrowser,通过使用WebBrowser控件的Execwb方法可实现打印预览、打印、进行页面设置等操作。
在页面中调用控件WebBrowser的语句如下:
下面介绍WebBrowser控件的Execwb方法,该方法主要用于对IE浏览器页面的操作。
语法:
WebBrowser.ExecWB nCmdID,nCmdExecOpt,[pvaIn],[pvaOut]
nCmdID:表示执行命令的ID号。
nCmdExecOpt:命令执行的参数,一般设定其值为1。
下面介绍一些常用的命令(其中WebBrowser表示调用WebBrowser控件时定义的ID名称),如:
WebBrowser.Execwb(1,1)表示打开页面。
WebBrowser.Execwb(2,1)表示关闭现在所有的IE窗口,并打开一个新窗口。
WebBrowser.Execwb(4,1)表示保存网页。
WebBrowser.Execwb(6,1)表示打印页面。
WebBrowser.Execwb(6,6)表示直接打印页面。
WebBrowser.Execwb(7,1)表示打印预览页面。
WebBrowser.Execwb(8,1)表示进行打印页面设置。
WebBrowser.Execwb(10,1)表示查看页面属性。
WebBrowser.Execwb(22,1)表示刷新页面。
WebBrowser.Execwb(45,1)表示关闭窗体时无提示。
读者可根据实际情况,通过设置Execwb方法中各参数值,对页面进行各项操作。
Microsoft Word是Microsoft提供的优秀文档处理软件,它在处理文档、资料过程中都显现出其强大的处理功能。在JSP中可以调用Word的相关对象实现报表打印的功能。下面介绍Word的Application对象、Document对象和Range对象的常用属性和方法。
l Word的Application对象
通过Application对象,可以访问由应用程序提供的所有其他对象。下面介绍Application对象的常用属性和方法。
² ActiveDocument属性
ActiveDocument属性返回一个表示活动文档或具有焦点的文档的Document对象。
² ActivePrinter属性
通过ActivePrinter属性可以设置或返回活动打印机的名称。
² ActiveWindow属性
ActiveWindow属性返回具有焦点的窗口。
² Caption属性
通过Caption属性设置或返回指定文档或应用程序窗口的标题文本。
² DisplayAlerts属性
DisplayAlerts属性用于指定代码运行时处理警报的方式。WdAlertlevel的取值有3个,分别为WdAlertsAll(默认值,显示所有消息和警报)、wdAlertsMessageBox(只显示消息框)和wdAlertsNone(不显示任何警报或消息框)。如果在执行程序之前将DisplayAlerts属性值设置为wdAlertsNone,程序将在无任何消息和警报的情况下执行,但在完成指定代码后,要确保将DisplayAlerts设置为WdAlertsAll。如:
Application.DisplayAlerts=Word.WdAlertLevel.wdAlertsAll
² DisplayStatusBar属性
DisplayStatusBar属性用于指示是否显示状态栏,返回一个Boolean类型值,为读/写属性。
² Path属性
Path属性返回当前应用程序的路径。
² Visible属性
Visible属性用于打开或关闭Word应用程序自身的显示,为读/写属性。当设置Visible属性值为False时,将隐藏所有打开的Word窗口,但实际Word应用程序仍在运行。
² CheckSpelling方法
CheckSpelling方法用于检查字符串的拼写是否有误。如果发现错误则返回True,如果未发现错误则返回False。
² Move方法
通过设置Move方法中的Left和Top参数(二者均为Integer值)移动应用程序主窗口。
² Resize方法
通过设置Resize方法Width和Height参数(二者以磅为单位)调整应用程序主窗口的大小。
² Quit方法
调用Quit方法将退出Word应用程序。WdSaveOptions的取值有3个,分别为wdSaveChanges(自动保存更改)、wdPromptToSaveChanges(提示保存更改)和wdDoNotSaveChanges(不保存更改退出)。如:
Application.Quit(Word.WdSaveOptions.wdPromptToSaveChanges)
l Word的Document对象
Microsoft Word中的大多数编程活动都将涉及Document对象或其相关内容。在Word中处理某个特定文档时,这个文档就称为活动文档,这时可通过Application对象的ActiveDocument属性创建Document对象。所有的Document对象同时也是Application对象的Documents集合的成员,该集合由所有打开的文档组成。使用Document对象时,允许使用单个文档,而Document集合则允许使用所有打开的文档。由于通过Application对象和Document对象都可以进行文档操作,所以Application对象和Document对象共享许多成员。
文档是由字符组成,字符排列成词,词构成句子,句子排列成段落,段落依次排列而构成节。Document对象具有一些映射到这些构造的集合,如Characters集合、Words集合、Sentences集合、Paragraphs集合、Sections集合、HeadersFooters集合。
l Word的Range对象
Range对象表示文档中的一个连续区域,此区域由起始字符位置和终止字符位置定义。指定的区域可以小至插入点、大至整个文档,还可以是当前选定内容代表的区域。可以定义一个Range对象,也可以在单个文档中定义多个Rang对象。Range对象中的字符包括非打印字符,如空格、回车符和段落标记。创建Range对象后,可以对在指定范围内搜索字符、设置文本格式等。
Microsoft Excel是Microsoft提供的用于办公管理的应用软件,它具有强大的表格统计功能。在JSP应用程序中,通过在JavaScript脚本中应用Application对象、Workbook对象、Worksheet对象和Range对象的相关属性和方法,可以实现将Web页面中的数据写入到Excel,然后由Excel自动打印报表内容的功能。
l Excel的Application对象
Excel的Application对象是Excel对象模型中的顶级对象。使用Application 对象可以确定或指定应用程序级属性或执行应用程序级方法。Application对象也是访问Excel对象模型的其它部分的转入点。
在JavaScript脚本中应用ActiveXObject()构造函数可以创建一个Excel.Application对象的实例,即显式引用Application对象。下面介绍Application对象的常用属性和方法。
² ActiveCell属性
ActiveCell属性返回一个Range对象,该对象代表活动窗口的活动单元格,或指定窗口的活动单元格。如果该窗口显示的不是工作表,则该属性无效。此属性为只读属性。下列表达式都是返回活动单元格,并都是等价的。
ActiveCell
或
Application.ActiveCell
或
ActiveWindow.ActiveCell
或
Application.ActiveWindow.ActiveCell
如果不指定对象识别符,本属性返回的是活动窗口中的活动单元格。活动单元格与选定区域是有区别的:活动单元格是当前选定区域内的单个单元格;选定区域可能包含多个单元格,但其中只有一个是活动单元格。
² ActiveChart属性
ActiveChart属性返回Chart对象,该对象代表活动图表(包括嵌入式图表或图表工作表)。当选定或激活嵌入式图表时,该嵌入式图表就成为当前活动的图表。如果当前没有活动的图表,本属性返回值为Nothing。此属性为只读属性。
语法:
[对象识别符.]ActiveChart
如果未指定对象识别符,此属性返回活动工作簿上的活动图表。
² ActivePrinter属性
通过ActivePrinter属性可以设置或返回活动打印机的名称。此属性为可读写的字符串类型。
语法:
Application.ActivePrinter
² ActiveSheet属性
ActiveSheet属性返回一个Worksheet对象,该对象代表活动工作簿中的,或者指定窗口工作簿中的活动工作表。如果没有活动的工作表,将返回值Nothing。此属性为只读属性。
语法:
[对象识别符.]ActiveSheet[.对象对应的属性]
如果未给出对象识别符,此属性返回活动工作簿中的活动工作表。
如果某一工作簿在若干个窗口中出现,那么该工作簿的ActiveSheet属性在不同窗口中的返回值可能不同。
² ActiveWorkbook属性
ActiveWorkbook属性返回一个Workbook对象,该对象代表活动窗口的工作簿。此属性为只读属性。
语法:
[对象识别符.]ActiveWorkbook[.对象对应的属性]
如果没有打开任何窗口、活动窗口、信息窗口或剪贴板窗口,则此属性返回值Nothing。
² Cells属性
Cells属性返回Range对象,该对象代表活动工作表中所有的单元格。此属性为只读属性。
语法:
[对象识别符.]Cells
如果当前活动文档不是工作表,则不能读取此属性值。
² Charts属性
Charts属性返回一个Sheets集合,该集合代表活动工作簿中的所有图表工作表。此属性为只读属性。
语法:
[对象识别符.] Charts
如果不给出对象识别符,将返回活动工作簿中所有的图表工作表。
² Columns属性
Columns属性返回一个Range对象,该对象代表活动工作表中的所有列。此属性为只读属性。
语法:
[对象识别符.]Columns
如果活动文档不是工作表,则Columns属性无效。
² Parent属性
Parent属性返回指定对象的父对象。此属性为只读属性。
语法:
对象识别符.Parent
² Path属性
Path属性是将Microsoft Excel的完整路径返回给应用程序,但不包括末尾的分隔符和应用程序名称。字符串类型,并为只读属性。
语法:
对象识别符.Path
² Range属性
Range属性返回一个Range对象,该对象代表一个单元格或单元格区域。
语法:
对象识别符.Range(Cell1,Cell2)
Cell1:表示区域名称,为必填项。可包括区域操作符(冒号)、相交区域操作符(空格)或合并区域操作符(逗号)。可在区域中任一部分使用局部定义名称。
Cell2:表示区域左上角和右下角的单元格,为可选项。可以是一个包含单个单元格、整列或整行的Range对象,或是一个用宏语言为单个单元格命名的字符串。
如果没有使用对象识别符,则该属性是ActiveSheet.Range
的快捷方式(它返回活动表的一个区域,如果活动表不是一张工作表,则该属性无效)。
² Rows属性
Rows属性返回代表活动工作表所有行的Range对象。如果活动文档不是工作表,Rows属性无效。
语法:
[对象识别符.]Range
在不用对象识别符的情况下使用此属性等价于ActiveSheet.Rows
。
² Sheets属性
Sheets属性返回代表活动工作簿中所有工作表的Sheets集合。
语法:
[对象识别符.]Sheets
在不用对象识别符的情况下,使用此属性等价于使用ActiveWorkbook.Sheets
。
² UserControl属性
如果应用程序可见,或者用户已创建或启动应用程序,则UserControl属性值为True。如果以编程方式通过CreateObject函数或GetObject函数启动应用程序,且应用程序为隐藏状态,则该属性值为False。此属性可读写。
语法:
对象识别符.UserControl[=属性值]
当对象的UserControl属性为False时,则最后一个以编程方式对该对象的引用释放以后,该对象也将释放。如果属性值为False,并且会话中最后一个对象已释放,Microsoft Excel将自动退出。
² Workbooks属性
Workbooks属性返回一个Workbooks集合,此集合代表所有打开的工作簿。此属性为只读属性。
语法:
[对象识别符.]Workbooks
在不使用对象识别符的情况下,使用此属性等价于Application.Workbooks
。
由Workbooks属性返回的集合并不包含打开的加载宏(一种特殊的隐藏工作簿)。但如果已知宏文件名,则可返回单个打开的加载宏。例如,Workbooks("mr.xla")
将打开的名为“mr.xla
”的加载宏作为Workbook属性返回结果。
² Quit方法
调用Quit方法将退出Microsoft Excel。
语法:
对象识别符.Quit
使用此方法时,如果有未保存的工作簿处于打开状态,则Microsoft Excel将弹出一个对话框,询问是否要保存所作的更改。为避免这一情况,可在使用Quit方法前保存所有的工作簿或将DisplayAlerts属性(宏运行时Microsoft Excel显示特定的警告和消息,默认值为True)设置为False,这时在Microsoft Excel退出时,即使有未保存的工作簿,也不会显示对话框,而且不保存就执行退出命令。
l Excel的Workbook对象
Workbook对象表示一个.xls或.xla工作簿文件。使用Workbook对象可以处理单个Excel工作簿。使用Workbooks集合可以处理所有当前打开的Workbook对象。
使用Application对象的ActiveWorkbook属性可以返回对当前活动工作簿的引用。Workbooks集合中的Count属性可以用于确定打开了多少可见工作簿和隐藏工作簿。默认情况下,Excel通常有一个名为Personal.xls的隐藏工作簿,它是由Excel作为宏的存储位置而创建的。如果隐藏的Personal.xls工作簿是唯一打开的工作簿,ActiveWorkbook属性将返回字符串Nothing;而Workbooks集合的Count属性将返回1,仅当没有打开的隐藏或可见工作簿时,Workbooks集合的Count属性才返回0。
下面介绍几个Workbook对象的常用方法。
² Add方法
使用Workbooks集合的Add方法可以创建新的Workbook对象。Add方法不但创建新的工作簿,同时立即打开创建的工作簿。调用Add方法还将返回一个表示创建的新工作簿的对象变量。
语法:
对象识别符.Add
² SaveAs方法
调用Workbook对象的SaveAs方法并指定要保存的工作簿的名称,可以保存新工作薄。如果已存在该名称的工作簿,则调用此方法时将出现错误。使用SaveAs方法保存工作簿之后,可以使用Workbook对象的Save方法来保存其它更改,也可以使用SaveCopyAs方法用另一个文件名来保存现有工作簿的副本。
Workbook对象的Name属性是一个由Excel指定的值,使用SaveAs方法保存新工作簿后,Name属性将为SaveAs方法的Filename参数中提供的名称。Name属性是只读的,所以要更改工作簿的名称,必须再次使用SaveAs方法,并在Filename参数中传递另一个值。
语法:
对象识别符.SaveAs(Filename)
Filename:表示保存的Excel文件名称。
注意:Workbook对象的FullName属性包含对象的路径和文件名,而Path属性只包含当前工作簿的已保存路径。保存新工作簿之前,FullName属性值与Name属性值相同,而Path属性不包含任何值。
² Open方法
Workbooks集合的Open 方法可用于打开现有的工作簿。使用Open方法打开工作簿后,该工作簿即成为活动工作簿。在调用Open方法时可以指定文件名。
语法:
对象识别符.Open(Filename)
² Close方法
Workbook对象的Close方法用于关闭打开的工作簿。
语法:
对象识别符.Close
注意:Workbook对象的Saved属性返回的是一个布尔值,该值指明该工作簿是否已经保存。对于任何新建或已打开但没有进行任何更改的工作簿,Saved属性将为True;对于包含未保存更改的工作簿,该属性则为False。可以将Saved属性设置为True,这样做的用处在于,当用户关闭工作簿时如果未对工作簿做任何更改,可以不提示用户保存更改。
l Excel的Worksheet对象
用户在Microsoft Excel中进行的大多数工作是在工作表环境中进行的。工作表包含用于处理数据的单元格,以及数百个用于处理工作表中数据的属性、方法和事件。
Worksheet对象包含在Worksheets集合中,可以使用Workbook对象的Worksheets属性来访问工作表中的数据。通过Workbook对象的Worksheets属性可以返回工作簿中所有工作表的集合,通过Workbook对象的Sheets属性可以返回工作簿中所有工作表和图表工作表的集合。
使用Worksheets集合的Add方法,可以将一个或多个工作表添加到Worksheets集合中;调用Worksheet对象的Delete方法可以从Worksheets集合中删除工作表。
l Excel的Range对象
在Microsoft Excel中,Range对象是功能强大、应用灵活的对象之一。
就对象而言,Excel的Range对象是比较独特的。一个区域在不同情况下可以是不同的事物,Range对象可以是单个单元格或单元格集合;可以是单个对象或对象的集合;可以是某个行或列;可以表示三维的跨多个工作表的单元格集合。此外,与其他对象不同,工作簿或工作表中不存在包含所有Range对象的Ranges集合。
企业的一些信息通过网络形成HTML报表,虽然IE可以直接打印显示在其中的内容,但是从界面上来看,如果直接将HTML的显示结果打印出来,显的不够美观,如果将它转换成PDF文件打印,则效果会好很多。
iText组件是开源项目,不仅可以生成PDF、RTF文件格式,而且可以将XML、HTML文件格式转化为PDF文件格式。下载地址是http://www.lowagie.com/iText/download.html,进入下载界面,如图6.1所示。
图6.1 iText类库的下载链接
本书提供的iText组件版本是2.0.2,读者可以自行下载最新版本的iText组件,“iText-2.0.2.jar”适用Windows操作系统,“iText-2.0.2.tar.gz”适用于Linux操作系统。下载iText-2.0.2.jar文件后,需要把itext-2.0.2.jar包放入项目目录下的WEB-INF/lib路径中,这样在程序中就可以使用iText类库了。如果生成的PDF文件中需要出现中文、日文、韩文字符,则需要访问http://itext.sourceforge.net/downloads/iTextAsian.jar下载iTextAsian.jar包。当然,如果想真正了解iText组件,阅读iText文档显得非常重要,读者在下载类库的同时,也可以下载类库文档。
用iText组件生成PDF文件需要以下5个步骤:
(1)建立com.lowagie.text.Document对象的实例。
Document document=new Document();
(2)建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。
pdfWriter.getInstance(document, new FileOutputStream("Helloworld.PDF"));
(3)打开文档。
document.open();
(4)向文档添加内容。
document.add(new Paragraph("Hello World"));
(5)关闭文档。
document.close();
下面笔者详细介绍iText组件的属性和用法。
(1)建立com.lowagie.text.Document对象的实例,该对象的构造方法有三个。
public Document();
public Document(Rectangle pageSize); //定义页面的大小
public Document(Rectangle pageSize,int marginLeft,int marginRight,int marginTop,int marginBottom); /*定义页面的大小,参数marginLeft、marginRight、marginTop、marginBottom分别为左、右、上、下的页边距*/
通过Rectangle类对象的参数可以设定页面大小、面背景色、以及页面横向/纵向等属性。iText组件定义了A0-A10、AL、LETTER、HALFLETTER、_11x17、LEDGER、NOTE、B0-B5、ARCH_A-ARCH_E、FLSA和FLSE等纸张类型,也可以制定纸张大小来自定义,程序代码如下:
Rectangle pageSize = new Rectangle(144,720);
在iText组件中,可以通过下面的代码实现将PDF文档设定成A4页面大小, 当然,也通过Rectangle类中的rotate()方法可以将页面设置成横向。程序代码如下:
Rectangle rectPageSize = new Rectangle(PageSize.A4);
//定义A4页面大小
rectPageSize = rectPageSize.rotate();
//加上这句可以实现A4页面的横置
Document doc = new Document(rectPageSize,50,50,50,50);
//其余4个参数,设置了页面的4个边距
(2)书写器(Writer)对象。
一旦文档(document)对象建立好之后,需要建立一个或多个书写器与对象相关联,通过书写器可以将具体的文档存盘成需要的格式,例如,om.lowagie.text.PDF.PDFWriter可以将文档存成PDF格式,而com.lowagie.text.html.HTMLWriter可以将文档存成HTML格式。
(3)设定文档属性。
在文档打开之前,可以设定文档的标题、主题、作者、关键字、装订方式、创建者、生产者、创建日期等属性,调用的方法分别是:
public boolean addTitle(String title)
public boolean addSubject(String subject)
public boolean addKeywords(String keywords)
public boolean addAuthor(String author)
public boolean addCreator(String creator)
public boolean addProducer()
public boolean addCreationDate()
public boolean addHeader(String name, String content)
其中方法addHeader()对于PDF文档无效,addHeader()方法仅对HTML文档有效,用于添加文档的头信息。
当新的页面产生之前,也可以设定页面的大小、书签、脚注(HeaderFooter)等信息,调用的方法是:
public boolean setPageSize(Rectangle pageSize)
public void setHeader(HeaderFooter header)
public void resetHeader()
public void setFooter(HeaderFooter footer)
public void resetFooter()
public void resetPageCount()
public void setPageCount(int pageN)
如果要设定第一页的页面属性,这些方法必须在文档打开之前调用。
(4)文本处理。
iText组件中用文本块(Chunk)、短语(Phrase)和段落(paragraph)处理文本。文本块(Chunk)是处理文本的最小单位,有一串带格式(包括字体、颜色、大小)的字符串组成。如以下代码就是产生一个字体为HELVETICA、大小为12、带下划线的字符串:
Chunk chunk1 = new Chunk("This text is underlined",FontFactory.getFont(FontFactory.HELVETICA, 12, Font.UNDERLINE));
短语(Phrase)由一个或多个文本块(Chunk)组成,短语(Phrase)也可以设定字体,但对于其中已经设定过字体的文本块(Chunk)无效。通过短语(Phrase)成员函数add()可以将一个文本块(Chunk)加到短语(Phrase)中,如phrase6.add(chunk)。
段落(paragraph)由一个或多个文本块(Chunk)或短语(Phrase)组成,相当于Word文档中的段落概念,同样可以设定段落的字体大小、颜色等属性。另外也可以设定段落的首行缩进、对齐方式(左对齐、右对齐、居中对齐)。
(5)中文处理。
为了解决中文输出的问题,需要多下载一个iTextAsian.jar组件。下载后放入项目目录下的WEB-INF/lib路径中。使用这个中文包无非是实例化一个字体类,把字体类应用到相应的文字中。可以正常显示中文。
为了输出中文,可以通过以下代码进行解决:
其中:“STSong-Light”定义了使用中文字体,“UniGB-UCS2-H”定义文字的编码标准和样式,GB代表编码方式为gb2312,H是代表横排字,V代表竖排字。
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
用这个中文的基础字体实例化了一个字体类。
Font FontChinese = new Font(bfChinese, 12, Font.NORMAL);
将字体类用到了一个段落中。
Paragraph par = new Paragraph("世界您好",FontChinese)
将段落添加到了文档中。
document.add(par);
(6)表格处理。
iText组件中处理表格的类为:com.lowagie.text.Table和com.lowagie.text.PDF.PDFPTable,对于比较简单的表格处理可以使用com.lowagie.text.Table,但是如果要处理复杂的表格,这里就需要用到com.lowagie.text.PDF.PDFPTable进行处理。
下面简要说明一下com.lowagie.text.Table类。类com.lowagie.text.Table的构造函数有3个:
Table(int columns)
Table(int columns, int rows)
Table(Properties attributes)
参数columns、rows、attributes分别为表格的列数、行数、表格属性。创建表格时必须指定表格的列数,而对于行数可以不用指定。
建立表格之后,可以设定表格的属性,如边框宽度、边框颜色、衬距(padding space 即单元格之间的间距)大小等属性。程序代码如下:
Table table = new Table(3);//建立列数为3的表格
table.setBorderWidth(1);//边框宽度设置为1
table.setBorderColor(new Color(0, 0, 255));//颜色为蓝色
table.setPadding(5);//表格边距离为5
table.setSpacing(5);
Cell cell = new Cell("header");//创建单元格作为表头
cell.setHeader(true);//表示该单元格作为表头信息显示
cell.setColspan(3);//该单元格占用3个列
table.addCell(cell);
table.endHeaders();//表头添加完毕,必须调用此方法,否则跨页时,表头联显示
cell = new Cell("example cell with colspan 1 and rowspan 2");//添加一个一行两列的单元格
cell.setRowspan(2);//向下占用2行
cell.setBorderColor(new Color(255, 0, 0));
table.addCell(cell);
table.addCell("1.1");
table.addCell("2.1");
table.addCell("1.2");
table.addCell("2.2");
table.addCell("cell test1");
cell = new Cell("big cell");
cell.setRowspan(2);//向下占用2行
cell.setColspan(2);//占用2列
table.addCell(cell);
table.addCell("cell test2");
在表格中添加单元格时,按照自左向右,自上而下的顺序添加,添加完如上代码后,表格的右下方出现2行2列的空白,这是再向表格添加单元格时,先填满这个空白,然后再另起一行,否则会出现异常。这里需要补充说明的是iText组件的一个特点,iText组件出错的时候,不会有任何提示,只会把出错的部分跳过去,例如在没有用中文字体的情况下输出中文,中文部分的段落会是空白的。
iText组件中一个文档(Document),可以有很多个表格,一个表格可以有很多个单元格,一个单元格里面可以放很多个段落,一个段落里面可以放一些文字。但是,读者必须明确以下两点内容:
在iText组件中没有行的概念,一个表格里面直接放单元格,如果一个3列的表格中放进6个单元格的话,那么就是两行的表格。
如果一个3列的表格放入5个最基本的没有任何跨列设置的单元格,表格会出错,例如,面说的,表格根本添加不到文档中,而且不会有任何提示。
下面来看如何使用com.lowagie.text.PdfPTable类来实现3行3列表格,通过PdfPTable类定义3列表格,并通过for语句循环输出表格。程序代码如下:
<%@ page
import="Java.io.*,Java.awt.Color,com.lowagie.text.*,com.lowagie.text.pdf.*"%>
<%
response.reset();
response.setContentType("application/pdf");
Document document = new Document();
//设置表格的形式
PdfPTable table = new PdfPTable(3);
for (int i = 0; i < 9; i++) {
PdfPCell cell = new PdfPCell();
cell.addElement(new Paragraph(String.valueOf(i)));
table.addCell(cell);
}
%>
(7)图像处理。
iText组件中处理表格的类为com.lowagie.text.Image,目前iText组件支持的图像格式有:GIF, Jpeg, PNG, wmf等格式,对于不同的图像格式,iText组件用同样的构造函数自动识别图像格式,通过下面的代码分别获得gif、jpg、png图像的实例。
Image gif = Image.getInstance("vonnegut.gif");
Image jpeg = Image.getInstance("myKids.jpg");
Image png = Image.getInstance("hitchcock.png");
图像的位置主要是指图像在文档中的对齐方式、图像和文本的位置关系。iText组件中通过方法setAlignment(int alignment)进行处理,当参数alignment为Image.RIGHT、Image.MIDDLE、Image.LEFT分别指右对齐、居中、左对齐;当参数alignment为Image.TEXTWRAP、Image.UNDERLYING分别指文字绕图形显示、图形作为文字的背景显示,也可以使这两种参数结合使用以达到预期的效果,如setAlignment(Image.RIGHT|Image.TEXTWRAP)显示的效果为图像右对齐,文字围绕图像显示。
如果图像在文档中不按原尺寸显示,可以通过下面的方法进行设定:
public void scaleAbsolute(int newWidth, int newHeight)
public void scalePercent(int percent)
public void scalePercent(int percentX, int percentY)
方法scaleAbsolute(int newWidth, int newHeight)直接设定显示尺寸;方法scalePercent(int percent)设定显示比例,如scalePercent(50)表示显示的大小为原尺寸的50%;而方法scalePercent(int percentX, int percentY)则图像高宽的显示比例。
如果图像需要旋转一定角度之后在文档中显示,可以通过方法setRotation(double r)设定,参数r为弧度,如果旋转角度为30度,则参数r= Math.PI / 6。
(8)关于response.getOutputStream()抛出IllegalStateException异常。
response.reset();
response.setContentType("application/pdf");
DataOutput output = new DataOutputStream(response.getOutputStream());
抛出异常结果如下:
ERROR [Engine] StandardWrapperValve[jsp]: Servlet.service() for servlet jsp threw exceptionJava.lang.IllegalStateException: getOutputStream() has already been called for this response
造成上述异常的主要是Web容器生成的Servlet代码中有out.write(""),这个和JSP中调用的response.getOutputStream()产生冲突,即Servlet规范说明,不能既调用response.getOutputStream(),又调用response.getWriter(),无论先调用哪一个,在调用第二个时候都会抛出IllegalStateException异常,因为在JSP中,out变量是通过response.getWriter()方法得到的,在程序中既用了response.getOutputStream,又用了out变量,故出现以上错误。
解决方案,程序中添加程序代码如下即可:
out.clear();
out=pageContext.pushBody();
POI组件适合把Web报表或数据库中的值直接传入Excel中进行描述,同时可以做打印操作,POI组件的主页是http://Jakarta.apache.org/poi/hssf/index.html,也可以通过http://www.apache.org/dyn/closer.cgi/jakarta/poi/网址进行下载,本书采用的POI组件的版本是poi-bin-3.0-FINAL-20070503.zip,读者可以自行下载最高版本。解压缩*.zip文件,把3个jar包放入项目目录下的WEB-INF/lib路径中即可使用POI组件。虽然POI组件在某些细节有些小Bug并且不支持写入图片,但其他方面还是不错的。
POI项目实现的Excel文件格式称为HSSF,它是Horrible SpreadSheet Format的缩写,即“讨厌的电子表格格式”。通过HSSF,可以使用纯Java代码读取、写入、修改Excel文件。
先来看POI组件基本属性,以下可能需要使用到如下的类:
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFDataFormat;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
(1)创建workbook(工作簿)。
HSSFWorkbook wb = new HSSFWorkbook();//使用默认的构造方法创建workbook
FileOutputStream fileOut = new FileOutputStream("workbook.xls");//指定文件名
wb.write(fileOut);//输出到文件
fileOut.close();
(2)创建一个sheet(工作表)。
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet1 = wb.createSheet("new sheet");//workbook创建sheet
HSSFSheet sheet2 = wb.createSheet("second sheet");//workbook创建另外的sheet
FileOutputStream fileOut = new FileOutputStream("workbook.xls");
wb.write(fileOut);
fileOut.close();
(3)创建cells(单元格)。
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("new sheet");//注意以下的代码很多方法的参数是short 而不是int 所以需要做一次类型转换
HSSFRow row = sheet.createRow((short)0);//sheet 创建一行
HSSFCell cell = row.createCell((short)0);//行创建一个单元格
cell.setCellValue(1);//设定单元格的值
row.createCell((short)1).setCellValue(1.2); //值的类型参数有多种double ,String ,boolean,
row.createCell((short)2).setCellValue("This is a string");
row.createCell((short)3).setCellValue(true);// Write the output to a file
FileOutputStream fileOut = new FileOutputStream("workbook.xls");
wb.write(fileOut);
fileOut.close();
(4)建日期cells。
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("new sheet");
HSSFRow row = sheet.createRow((short)0);
HSSFCell cell = row.createCell((short)0);//设定值为日期
cell.setCellValue(new Date());
HSSFCellStyle cellStyle = wb.createCellStyle();//指定日期显示格式
cellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat ("m/d/yy h:mm"));
cell = row.createCell((short)1);
cell.setCellValue(new Date());//设定单元格日期显示格式
cell.setCellStyle(cellStyle);
FileOutputStream fileOut = new FileOutputStream("workbook.xls");
wb.write(fileOut);
fileOut.close();
特别地,值得注意的是使用HSSF处理日期数据要小心。Excel内部以数值的形式保存日期数据,区别日期数据的惟一办法是通过单元格的格式。对于包含日期数据的单元格,cell.getCellType()将返回HSSFCell.CELL_TYPE_NUMERIC,不过利用工具方法HSSFDateUtil.isCellDateFormatted(cell)可以判断出单元格的值是否为日期。
也可以设置单元格样式,首先创建一个样式对象,然后把它指定给一个单元格,或者指定给具有相同样式的单元格。
HSSFCellStyle style = wb.createCellStyle();
style.setDataFormat(HSSFDataFormat.getBuiltinFormat("($#,##0_);[Red]($#,##0)"));
style.setFillBackgroundColor(HSSFColor.AQUA.index);
style.setFillPattern(HSSFCellStyle.BIG_SPOTS);
cell.setCellStyle(style);
当然,也可以在POI组件较新的版本中使用公式,例如:
HSSFCell cell2=row.createCell((short)4);
cell2.setCellFormula("SUM(B1:B2)");
这样,E1中的值为B1和B2的加和。
设定单元格格式,单元格格式的设定有很多形式包括单元格的对齐方式,内容的字体设置,单元格的背景色等。
HSSFCellStyle style = wb.createCellStyle();//创建一个样式
style.setFillBackgroundColor(HSSFCellStyle.AQUA);//设定此样式的的背景颜色填充
style.setFillPattern(HSSFCellStyle.BIG_SPOTS);//样式的填充类型。
//有多种式样如:
//HSSFCellStyle.BIG_SPOTS
//HSSFCellStyle.FINE_DOTS
//HSSFCellStyle.SPARSE_DOTS等
style.setAlignment(HSSFCellStyle.ALIGN_CENTER );//居中对齐
style.setFillBackgroundColor(HSSFColor.GREEN.index);//设定单元个背景颜色
style.setFillForegroundColor(HSSFColor.RED.index);//设置单元格显示颜色
style.setBorderBottom(HSSFCellStyle.BORDER_THIN);//带边框
HSSFCell cell = row.createCell((short) 1);
cell.setCellValue("X");
cell.setCellStyle(style);
行高、列宽设置:
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("new sheet");
HSSFRow row = sheet.createRow((short)0);
//设置到第10行
row.setRowNum (10);
//3是列号,4是列宽值
sheet.setColumnWidth(3, 4);
HSSF为读取操作提供了两类API:usermodel和eventusermodel,即“用户模型”和“事件-用户模型”。前者很好理解,后者比较抽象,但操作效率要高得多。usermodel主要有org.apache.poi.hssf.usermodel和org.apache.poi.hssf.eventusermodel包实现(在HSSF的早期版本中,org.apache.poi.hssf.eventusermodel属于eventmodel包)。
usermodel包把Excel文件映射成熟悉的结构,诸如Workbook、Sheet、Row、Cell等,它把整个结构以一组对象的形式保存在内存之中。eventusermodel模式要求用户熟悉文件格式的底层结构,另外,eventusermodel的API只提供读取文件的功能,也就是说不能用这个API来修改文件。
(1)通过usermodel读取文件。
用HSSF的usermodel读取文件很简单。首先创建一个InputStream,然后创建一个HSSFWorkbook:
InputStream myxls = new FileInputStream("workbook.xls"));
HSSFWorkbook wb = new HSSFWorkbook(myxls);
有了HSSFWorkbook实例,接下来就可以提取工作表、工作表的行和列,例如:
HSSFSheet sheet = wb.getSheetAt(0); // 第一个工作表
HSSFRow row = sheet.getRow(2); // 第三行
HSSFCell cell = row.getCell((short)3); // 第四个单元格
上面这段代码提取出第一个工作表第三行第四单元格。利用单元格对象可以获得它的值,提取单元格的值时请注意它的类型:
if (cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
System.out.println("单元格是字符串,值是: " + cell.getStringCellValue());
} else if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC) {
System.out.println("单元格是数字,值是: " + cell.getNumbericCellValue());
} else {
System.out.println("单元格的值不是字符串或数值。");
}
如果搞错了数据类型,程序将遇到异常。
(2)通过usermodel写入文件。
写入Excel文件比读取Excel文件还要简单。创建一个HSSFWorkbook实例,然后在适当的时候创建一个把文件写入磁盘的OutputStream:
HSSFWorkbook wb = new HSSFWorkbook();
FileOutputStream fileOut= new FileOutputStream("workbook.xls");
wb.write(fileOut);
fileOut.close();
创建工作表及其内容必须从相应的父对象出发,例如:
HSSFSheet sheet = wb.createSheet();
HSSFRow row = sheet.createRow((short)0);
HSSFCell cell = row.createCell((short)0);
cell.setCellValue(1);
row.createCell((short)1).setCellValue(1.2);
row.createCell((short)2).setCellValue("一个字符串");
row.createCell((short)3).setCellValue(true);
(3)通过HPSF读取文档属性。
读取文档属性其实并不复杂,因为Java程序可以利用POI项目的HPSF包。HPSF是 Horrible Property Set Format的缩写,译成中文就是“讨厌的属性集格式”。HPSF包是POI项目实现的读取属性工具,目前还不支持属性写入。
在Microsoft Word、Excel、PowerPoint等软件中,用户可以通过“文件”→“属性”菜单为文档添加附加信息,包括文档的标题、主题、摘要、类别、关键词等,同时应用软件本身还会加入最后访问的用户、最后访问和修改/打印的日期时间等信息。
文档的属性和正文是分开保存的。如前所述,文件内部就像是一个容器,里面包含许多类似目录和文件的结构,而POIFS就是用来访问其中的文件的工具。这些文件也称为流,文档的属性就保存在POIFS文件系统中专用的流里面。以一个Word文档为例,虽然在资源管理器中只看到一个叫做MyFile.doc的文档,其实在这个文档的内部,又包含了一个WordDocument、一个SummaryInformation和一个DocumentSummaryInformation文档;通常还会有其他的文档,这里暂且不管。
WordDocument包含了在Word里面编辑的文本,文档的属性保存在SummaryInformation和DocumentSummaryInformation流里面。也许将所有属性保存在单个文档里面看起来太简单了,所以Microsoft决心要使用两个流,为了使事情更复杂一点,这两个流的名字前面还加上了八进制的/005字符——这是一个不可打印的字符,因此前面就把它省略了。
Microsoft定义的标准属性有一个好处,它们并不在乎主文档到底是什么类型——不管是Word文档、Excel工作簿还是PowerPoint幻灯。只要知道如何读取Excel文档的属性,就知道了如何读取其他文档的属性。
对于读取Microsoft定义的标准属性,通过HPSF提供的API可以很方便地办到,本文只介绍读取标准属性的简单API,因为对大多数应用程序来说这已经完全足够了。请看代码:
public class ReadTitle{
public static void main(String[] args) throws IOException{
final String filename = args[0];
POIFSReader r = new POIFSReader();
r.registerListener(new MyPOIFSReaderListener(),"/005SummaryInformation");
r.read(new FileInputStream(filename));
}
static class MyPOIFSReaderListener implements POIFSReaderListener{
public void processPOIFSReaderEvent(POIFSReaderEvent event){
SummaryInformation si = null;
try{
si=(SummaryInformation)PropertySetFactory.create(event.getStream());
}
catch (Exception ex){
throw new RuntimeException
("属性集流/"" + event.getPath() +event.getName() + "/": " + ex);
}
final String title = si.getTitle();
if (title != null)
System.out.println("标题: /"" + title + "/"");
else
System.out.println("该文档没有标题.");
}
}
}
main()方法利用POIFS的事件系统从命令行指定的OLE 2文档读取名为/005SummaryInformation的流,当POIFSReader 遇到这个流时,它把控制传递给MyPOIFSReaderListener的processPOIFSReaderEvent()方法。
processPOIFSReaderEvent()通过参数获得一个输入流,该输入流包含了文档标题等属性。为了访问文档的属性,从输入流创建一个PropertySet实例。
si = (SummaryInformation) PropertySetFactory.create(event.getStream());
event.getStream()从POIFSReader传入的POIFSReaderEvent获得输入流。
以刚才获得的输入流为参数,调用PropertySetFactory的静态方法create()。正如其名字所暗示的,PropertySetFactory是一个工厂类,此类有一台“机器”能够把一个输入流转换成一个PropertySet实例,这台机器就是create()方法。
将create()方法返回的PropertySet定型(cast)成为SummaryInformation。PropertySet提供了按照一般办法读取属性集的各种机制,SummaryInformation类是PropertySet的子类,即SummaryInformation类在PropertySet类的基础上增加了操作Microsoft标准属性的便捷方法。
在这个处理过程中,可能引起错误的因素很多,因此把这部分内容放入了一个try块——不过这个示例程序只按照最简单的方式处理了异常,在实际应用中,最好能够对可能出现的不同异常类型分别处理。除了一般的I/O异常之外,还有可能遇到HPSF特有的异常,例如,如果输入流不包含属性集或属性集非法,就会抛出NoPropertySetStreamException异常。
有一种错误不太常见,但也不是绝无可能——/005SummaryInformation包含一个合法的属性集,但并不是摘要信息属性集。如果出现这种情况,则定型成SummaryInformation操作会失败,引发ClassCastException异常。
获得SummaryInformation实例之后,剩下的事情就很简单了,只要调用getTitle()方法,然后输出结果。
除了getTitle()之外,SummaryInformation还包含其他一些便捷方法,例如getApplicationName()、getAuthor()、getCharCount()、和getCreateDateTime()等。HPSF的JavaDoc文档详细说明了所有这些方法。
(4)读取文档摘要信息。
并非所有的属性都保存在摘要信息属性集之中。许多(但不是全部)OLE 2文件还有另一个属性集,称为“文档摘要信息”,对应的流是/005DocumentSummaryInformation。这个属性集保存的属性包括文档的类别、PowerPoint幻灯的多媒体剪辑数量等。
需要访问文档摘要信息属性集,程序的处理过程也与上例相似,只是注册的目标应该改成/005DocumentSummaryInformation流,有时,可能想要同时注册到摘要信息和文档摘要信息这两个流。这里的处理方式和前面的例子差不多,应该把包含文档摘要信息的流传递给PropertySetFactory.create(),但这次工厂方法将返回一个DocumentSummaryInformation对象(而不是前面例子中的SummaryInformation对象)。如果同时注册到了两个流,注意检查返回值的具体类型,或者使用Java的instanceof操作符,或者使用专用的isSummaryInformation()和isDocumentSummaryInformation()方法。记住,create()方法返回的总是一个PropertySet对象,因此总是可以对create()返回对象调用isSummaryInformation()和isDocumentSummaryInformation()方法。
iReport和JasperReport组件是完全的开源项目,是当今世面上比较流行的制作报表工具,它们的优势不仅仅因为是开源项目,还由于高性能,友好的人性化设计界面,使得报表变得不再千篇一律,适用于多种数据源,可以导出多种文件格式,例如HTML、PDF、Excel等文件格式。
iReport和JasperReport组件最主要能在设计时使数据和设计分开,实现Web报表的分页打印,分组合计,变量加减处理,输出格式化,用户须按照编写的规则编写一个XML,然后得到用户所要的报表文件。
IReport组件下载地址是http://ireport.sourceforge.net,本书提供的版本是iReport-2.0.0,读者可自行下载最新版本,下载后安装,目录中会有一个名为"iReport.bat"的批处理文件,双击运行它,如图6.2所示。
图6.2 iReport for JasperReport
若在JSP中使用iReport制作的报表需要另一个组件支持,这个组件就是JasperReport组件。
JasperReport下载地址是http://jasperreports.sourceforge.net ,JasperReport是一个灵活、功能强大的报表产生工具,可以以PDF、HTML或XML等多种形式产生报表,并支持CSV、XLS等格式报表,该引擎由Java编写,支持多种形式应用程序产生动态报表。JasperReport是按照一个预定义的XML文档来组织报表的数据,这些数据来源多样,包括关系型数据库、Java容器对象等。
首先简要介绍一下iReport组件的用法。
(1) 单击New Document创建一个新报表(也可以选Report Wizard报表向导来生成报表),也可以单击图标,来新建报表。
添写报表属性,在“报表名称”输入报表名字,在"预设尺寸"区内选择所要用的纸张及尺寸,在这里选择默认参数,可以修改"i18n"选项卡中的“XML 编码”,默认参数为"UTF-8"(如果认为有必要可以手动输入"GB2312"),如图6.3所示,然后单击OK按钮。
图6.3 iReport创建新报表
(2)设置数据源。
首先把SQL Server 2000连接Java的驱动包放入iReport组件所在目录的lib中,这样iReport组件才可以使用数据源,单击菜单"Data",选中"连接/资料来源",单击"New"设置数据源,弹出对话框,如图6.4所示。
图6.4 iReport设置数据源
设置连接属性,其中有JavaBean、XML、JDBC等数据源,在这里选择JDBC数据源,输入数据源名称"Name",可以任意输入名字。在"JDBC Driver "下拉列表框中选择数据库连接驱动,选择Java连接SQL Server 2000驱动,连接JDBC的URL,。在"Server Address"输入框中输入服务器地址。在"DataBase"输入框中填写数据库名字。以及填上登录数据库的用户名"UserName"和密码"Password",然后单击密码输入框中边上的"Save Password"保存密码,如果不保存,每次执行报表时都会弹出要求输入连接数据库的用户名密码的对话框。设置数据源如图6.5所示。
图6.5 iReport设置数据源
单击"Test"按钮,如果测试成功后会弹出如图6.6对话框, 否则就会弹出连接错误对话框,这时就要检查的设置参数是否正确。
图6.6 测试成功对话框
(3)iReport的中文处理。
iReport中文处理与iText组件相同,需要一个亚洲语言包"iTextAsian.jar"放到iReport目录下的lib目录中;修改报表的"PDF Font Name"将其改为"STSong-Light",再则是修改"PDF Embedded PDF Encoding" 改为"UniGB-UCS2-H(Chinese Simplified)" ,这样就可以解决iReport的中文问题。
(4)报表编译时产生NoClassDefFoundException异常。
在代码处理JasperReport时出现该异常,是因为在引用JasperReport的一些jar文件不完整,处理方式是将JasperReport目录下的Demo/samples/Webapp/WEB-INF/lib/所有的jar文件,全引用了,以防不测,但会有一些不必要的包被引用了,读者可以自己将不必要的jar包去掉。
(5)另存为PDF时出现UnisupportEncodingException异常。
这是因为没有改变"PDF Font Name"应改为"STSong-Light"所致。
(6)iReport把xml文件编译成jasper文件。
单击图标,进行编译,把*.jrxml编译成*.jasper文件,同时显示报表内容,但有一点需要说明,如果在JSP中使用*.jasper时,手动编译出的*.jasper不能应用到JSP中,需要使用JasperReport类库中的JasperCompileManager类中的compileReportToFile()静态方法实现编译,如下述代码所示。
JasperCompileManager.compileReportToFile(*.jrxml,*.jasper)
(7)设置iReport的输出格式。
在前面曾提到过iReport可以以多种格式进行输出,如PDF,HTML,XML,XLS,CVS等文件格式,选择iReport菜单栏上的“建立”,可以看到缺省格式是“JRViewer”预览,这是iReport自身带的PDF格式预览,读者可以根据自己的需要选择报表输出格式,选择其他格式的预览,系统会以这种格式保存到磁盘中。保存位置读者也可以选择,在iReport菜单栏中选择Options,在其中选择Compiler标签,选择编译好的文件以及各种形式的报表保存位置,如图6.7所示。
图6.7 iReport设置报表保存位置
(8)使用SQL语句查询。
大家都知道,数据查询必须要有查询语句,特别是综合查询时可能SQL语句会复杂一些, iReport组件可以使用查询语句输出报表,在菜单"Data"下选择“报表查询”项,添入查询语句,如图6.8所示。
图6.8 iReport组件使用sql语句
单击"Save query"生成SQL脚本存入磁盘。如果需要在SQL语句中使用参数,例如以“名称”进行模糊查询,传递的参数为name,那么期望的SQL语句可能是下面这个样子。
Select * from users where name like ‘%name%’
其实在iReport组件中也可以很容易实现, Select * from users where name like '% $P{name}%’ ,$P{name}是接下来要说明的参数。
(9)在iReport中设置参数
添加参数,读者可以右击Document下的Parameters,单击"Add",选择"Parameter",打开“添加参数”对话框,如图6.9所示。
图6.9 iReport添加参数
添加参数名称,选择参数类型,设置好后,单击"OK"按钮,如果想在JSP中为名为"name"的参数赋值,需要引用下面的程序代码:
Map parameters=new HashMap();
parameter.put("name","p");
(10)报表的动态对象变量、参数、字段。
在使用iReport的过程中会碰到很多与变量(Variables)、参数(Parameters)、字段(Fields)有关的内容,现在介绍这些对象的使用和意义。
字段(Fields):是数据库抽取出来的,希望在报表中出现的数据库内容。例如一个ID的所有值为$F{ID}。
参数(Parameters):这是给报表的提供入口,例如希望在报表被解释的时候提供where语句的条件值,那么就可以使用参数(Parameters),$P{ parameterName }。
变量(Variables):这是报表中一些逻辑运算的表现,例如统计值,$V{ variablesName }。
每种对象的定义格式如每个对象的后面说明。
Java Excel组件是专门制作Excel报表的开源组件,通过Java Excel组件Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。由于Java Excel组件是使用Java编写的,所以在Web应用中可以通过JSP、Servlet来调用API实现对Excel数据表的访问。
获得Java Excel开源包(jxl.jar)可以到http://www.andykhan.com/jexcelapi/download.html下载最新版本。解压缩,将jxl.jar复制到项目的WEB-INF/lib中即可使用Java Excel组件。
Java Excel 提供以下功能:
从Excel 95、97、2000等格式的文件中读取数据;读取Excel公式;生成Excel数据表;支持字体、数字、日期的格式化;支持单元格的阴影操作,以及颜色操作;修改已经存在的数据表。
如果想了解Java Excel组件,首先要了解Java Excel API,下面简单介绍Java Excel API中常用类提供的方法。
(1)Workbook类提供的方法。
getNumberOfSheet():返回值类型为int型,用来获得工作簿(workbook)中工作表(sheet)的个数。
getSheet(int index)和getSheet(String name):返回值类型为int型,分别通过工作表下标和名称。
getSheets():返回值类型为Sheet[]型,用来获得工作簿中所有工作表的名称。
(2)Sheet类提供的方法。
getname():返回值类型为String型,用来获得当前工作表名称。
getRows():返回值类型为int型,用来获得当前工作表的记录数。
getColumns():返回值类型为int型,用来获得当前工作表的字段数。
getCell(int column,int row):返回值类型Cell类型,用来获得当前工作表指定的单元格,需要注意的是第一参数为列,第二个参数为行。
getRow(int row):返回值类型为Cell[]型,用来获得当前工作表制定列的所有单元格。
getColumn(int column):返回值类型为Cell[]型,用来获得当前工作表指定列的所有单元格。
(3)Cell类提供方法。
getRow():返回值类型为int型,用来获得当前单元格所在行。
getColumn():返回值类型为int型,用来获得当前单元格所在列。
getType():返回值类型为CellType型,用来获得当前单元格值的类型。
getContents():返回值类型为String型,用来获得当前单元格的值。
getCellFormat():返回值类型为CellFormat型,用来格式化单元格内容。
下面简要介绍一下Java Excel的基本属性:
(1)从Excel文件读取数据表。
Java Excel既可以从本地系统文件(.xls)读取Excel数据表,也可以从输入流中读取Excel数据表。
首先创建Workbook(工作簿),程序代码如下:
import Java.io.*;
import jxl.*;
...//非关键代码省略
try{
//构建只读Workbook对象,从流创建Workbook
InputStream is=new FileInputStream(sourcefile);
jxl.Workbook rwb=Workbook.getWorkbook(is);
}catch(Exception e){
e.printStackTrace();
}
一旦创建了Workbook,就可以通过它来访问Excel Sheet(工作表),程序代码如下:
//获取第一张Sheet表
Sheet rs=rwb.getSheet(0);
既可以通过Sheet名称访问它,也可以通过下标访问它。如果使用下标访问时,要注意的一点是下标从0开始。
当得到了Sheet,就可以通过它来访问Excel Cell(单元格)。程序代码如下:
Cell c00=rs.getCell(0,0);//获取第一行,第一列的值
String strc00=c00.getContents();
Cell c10=rs.getCell(0,1);//获取第二行,第一列的值
String strc10=c10.getContents();
Cell c11=rs.getCell(1,1);//获取第二行,第二列的值
String strc10=c10.getContents();
Cell c01 = rs.getCell(1, 0); // 获取第一行,第二列的值
String strc01 = c01.getContents();
System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());
getCell(x,y)中的x代表列,y代表行。在得到Cell对象后,通过getType()方法可以获得该单元格的类型,然后与API提供的基本类型相匹配,强制转换成相应的类型,最后调用相应的取值方法getXXX(),就可以得到确定类型的值。
当完成对Excel电子表格数据的处理后,一定要使用close()方法来关闭先前创建的对象,以释放读取数据表的过程中所占用的内存空间,在读取大量数据时显得尤为重要。程序代码如下:
rwb.close();//操作完成时,关闭对象,释放占用的内存空间
(2)生成新的Excel工作簿。
与读取Excel工作表相似,首先要使用Workbook类的工厂方法创建一个可写入的工作簿(Workbook)对象,这里要注意的是,只能通过API提供的工厂方法来创建Workbook,而不能使用WritableWorkbook的构造函数,因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下:
import Java.io.*;
import jxl.*;
import jxl.write.*;
… //非关键代码省略
try{
//构建Workbook对象, 只读Workbook对象
//Method 1:创建可写入的Excel工作簿
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile));
//Method 2:将WritableWorkbook直接写入到输出流
/*
OutputStream os = new FileOutputStream(targetfile);
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os);
*/
}catch (Exception e){
e.printStackTrace();
}
Java Excel为写入文件提供了两种方式,第一种是生成本地文件,如果文件名不带绝对路径的话,缺省文件会定位在相对目录,如果文件名带有绝对路径的话,则生成的Excel文件会定位在相应目录;另一种是将Excel对象直接写入到输入流中,例如,用户通过浏览器来访问Web服务器,如果HTTP头设置正确的话,浏览器自动调用客户端的Excel应用程序,来动态显示生成的Excel表格。
接下来就是要创建工作表,创建工作表的方法与创建工作簿的方法几乎相同,同样是通过工厂模式方法获得相应的对象,该方法需要两个参数,一个是工作表的名称,另一个是工作表在工作簿中的位置,参考下面的代码片段:
jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);//创建Excel工作表
现在只需要实例化API所提供的Excel基本数据类型,将它添加到工作表中就可以。程序代码如下:
jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell");
ws.addCell(labelC);//添加label对象
jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF);
ws.addCell(labelCF);//添加带有字型Formatting的对象
jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
ws.addCell(labelN);//添加number对象
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
ws.addCell(labelNF); //添加带有formatting的Number对象
jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
ws.addCell(labelB); //添加Boolean对象
jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new Java.util.Date());
ws.addCell(labelDT);//添加DateTime对象
jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new Java.util.Date(), wcfDF);
ws.addCell(labelDTF);//添加带有formatting的DataFormat对象
注意:在构造单元格时,单元格在工作表中的位置已经确定了,一旦建立后,单元格位置是不可变更的,尽管单元格内容是可以改变的。
写入Excel工作表,程序代码如下:
wwb.write();
最后,不要忘记关闭Excel工作簿对象:
wb.close();//关闭工作簿对象
在关闭Excel对象时必须先调用write()方法,因为之前的操作都是存储在缓存中的,所以要通过write()方法将操作的内容写入Excel文件中。如果先关闭了Excel对象,那么Excel文件将什么都没有。
(3)拷贝、更新Excel工作簿
下面介绍如何更新一个已经存在的工作簿,主要分两个步骤,第一:构造只读的Excel工作簿,第二:利用已经创建的Excel工作簿创建新的可写入的Excel工作簿,当然这两个实例都是打开相同Excel文件,只是给予的权限不同,程序代码如下:
jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile));//创建只读的Excel工作簿的对象
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile), rw);//创建可以写入的Excel工作簿对象
jxl.write.WritableSheet ws = wwb.getSheet(0);//读取第一张工作表
jxl.write.WritableCell wc = ws.getWritableCell(0, 0);//获得第一个单元格对象
if(wc.getType() == CellType.LABEL){
Label l = (Label)wc;
l.setString("The value has been modified.");
}//判断单元格的类型,做出相应的转化
wwb.write();//写入Excel对象
wwb.close();//关闭可写入的Excel对象
rw.close();//关闭只读的Excel对象
之所以使用这种方式构建Excel对象,完全是因为效率的原因,为了提高性能,在读取工作表时,与数据相关的一些输出信息,所有的格式信息,如字体、颜色等等,是不被处理的,因为目的是获得行数据的值,既使没有了修饰,也不会对行数据的值产生什么影响。唯一的不利之处就是在内存中会同时保存两个同样的工作表,这样当工作表体积比较大时,会占用相当大的内存,但现在好像内存的大小并不是什么关键因素了。
一旦获得了可写入的工作表对象,就可以对单元格对象进行更新的操作了,在这里不必调用API提供的add()方法,因为单元格已经存在于工作表当中,所以只需要调用相应的setXXX()方法,就可以完成更新的操作了。
尽管单元格原有的格式化修饰是不能去掉的,还是可以将新的单元格修饰加上去,以使单元格的内容以不同的形式表现。
新生成的工作表对象是可写入的,除了更新原有的单元格外,还可以添加新的单元格到工作表中。最后,不要忘记调用write()方法,将更新的内容写入到文件中,然后关闭工作簿对象,这里有两个工作簿对象要关闭,一个是只读的,另外一个是可写入的。