|
|
请使用QQ关联注册PLM之家,学习更多关于内容,更多精彩原创视频供你学习!
您需要 登录 才可以下载或查看,没有账号?注册
x
通过VC实现对Excel表格的操作的方法有多种,如:通过ODBC数据库实现,通过解析Excel表格文件,通过OLE/COM的实现。本文主要研究通过OLE/COM实现对Excel表格的操作。
. O" O; Y6 Z! I: e8 J* u2 Z. [; x4 M+ s, _
* E$ J( ~5 D* e; v J' G8 a
1、添加OLE/COM支持。 首先,应用程序必须添加对OLE/COM的支持,才能导入OLE/COM组件。 本文使用的是MFC对话框程序,在创建工程的向导中选中Automation选项即可为程序自动添加相应的头文件和OLE库初始化代码。 通过查看源代码,可以知道在stdafx.h的头文件中,添加了OLE/COM很多类所需添加的头文件。 #include <afxdisp.h> // MFC 自动化类 同时,在应用程序类的InitInstance函数中,添加了OLE/COM的初始化代码,如下所示: // 初始化 OLE 库 if (!AfxOleInit()) { AfxMessageBox(IDP_OLE_INIT_FAILED); return FALSE; }
2 w' _# E! I7 q1 q7 p- Z; \7 S 2、导入并封装Excel中的接口 Excel作为OLE/COM库插件,定义好了各类交互的接口,这些接口是跨语言的接口。VC可以通过导入这些接口,并通过接口来对Excel的操作。 由于本文只关心对Excel表格中的数据的读取,主要关注几个_Application、Workbooks、_Workbook、Worksheets、_Worksheet、Range等几个接口。Excel的各类接口的属性、方法可以通过MSDN的Office Development进行查询。 VS2010导入OLE/COM组件的接口的步骤为:Project->Class Wizard->Add Class->MFC Class From TypeLib,先选择要导入的组件所在的路径,即Excel.exe所在的路径,然后再选择 要导入的Excel类型库中的接口。 在完成接口导入后,VS2010将自动为导入的接口创建相应的实现类,用于对接口属性和方法的实现。由于标准的C++没有属性访问器,只能添加一个两个存取函数来实现对属性的访问,通过在属性名称前加上get_和put_前缀分别实现对属性的读写操作。即,由VC自动完成C++类对接口的封装。
2 V$ {7 r5 t0 f' D本文所导入的接口对应的类和头文件的说明如下所示: * B9 h# }+ w' F, C2 U& U& Z
Excel接口 | 导入类 | 头文件 | 说明 | _Application | CApplicaton | Application.h | Excel应用程序。 | Workbooks | CWorkbooks | Workbooks.h | 工作簿的容器,里面包括了Excel应用程序打开的所有工作簿。 | _Workbook | CWorkbook | Workbook.h | 单个工作簿。 | Worksheets | CWorksheets | Worksheets.h | 单个工作簿中的Sheet表格的容器,包括该工作簿中的所有Sheet。 | _Worksheet | CWorksheet | Worksheet.h | 单个Sheet表格。 | Range | CRange | Range.h | 一定数量的单元格,可对单元格进行单个或多个单元格进行操作。 | 2 S- q6 W; J! j3 y2 C
" {3 F" ?* B3 {3 V
3、导入Excel的整个类型库 接口对应类只是对接口的属性和方法进行了封装,而Excel中的数据类型,如枚举类型却并为并不能使用,因此,为了更方便的操作Excel,还需要导入Excel的数据类型。 通过查看导入接口对应的头文件可以发现,在所有导入接口的头文件中,都会有这么行: #import "D: \\Program Files\\Microsoft Office\\Office12\\EXCEL.EXE" no_namespace 这行代码的作用是导入Excel整个类型库到工程中。 由VS2010自动产生的导入代码存在以下几个问题: (1)如果导入了多个接口,每个头文件都会把类型库导入一次,如果引用多个头文件,会导致类型库重复导入。 (2)Excel类型库中有些类型会跟MFC类库的某些类型冲突。 (3)Excel类型库的某些类型跟其他Office和VB的某些库相关,如果不导入相关库,将导致这些类型无法使用。。 以上三点问题的解决方法如下: (1)仅在_Application接口对应头文件中导入Excel类型库。 (2)对冲突的类型进行重命名。 (3)在导入Excel类型库之前,先导入Office和VB的相关库。 更改后的导入类型库的代码如下:
2 D, X' _7 A- K' Q7 e/*导入Office的类型库*/ #import "C: \\Program Files\\Common Files\\Microsoft Shared\\OFFICE12\\MSO.DLL" \ rename("RGB", "MSORGB") \ rename("DocumentProperties", "MSODocumentProperties") using namespace Office; ( V# w, b4 o$ o/ c- u" X
/*导入VB的类型库*/ #import "C: \\Program Files\\Common Files\\Microsoft Shared\\VBA\\VBA6\\VBE6EXT.OLB" using namespace VBIDE; : Q7 A/ F5 s. e7 O/ r' |# N
/*导入Excel的类型库*/ #import "D: \\Program Files\\Microsoft Office\\Office12\\EXCEL.EXE" \ rename("DialogBox", "ExcelDialogBox") \ rename("RGB", "ExcelRGB") \ rename("CopyFile", "ExcelCopyFile") \ rename("ReplaceText", "ExcelReplaceText") \ no_auto_exclude Using namespace Excel;
. X* m, q9 [+ [" I3 \3 X编译程序后,会在Deb UG或Release目录下生成三个文件mso.tlh、vbe6ext.tlh和excel.tlh。通过打开文件可知,该三个文件的命名空间分别是Office、VBIDE和Excel。导入了Excel的整个类型库后,就可以使用Excel中的所有类型了。
- U5 N$ P' |7 R3 j8 O4、操作Excel步骤 操作Excel的主要步骤如下: (1)创建一个Excel应用程序。 (2)得到Workbook的容器。 (3)打开一个Workbook或者创建一个Workbook。 (4)得到Workbook中的Worksheet的容器。 (5)打开一个Worksheet或者创建一个WorkSheet。 (6)通过Range对WorkSheet中的单元格进行读写操作。 (7)保存Excel。 (8)释放资源。
% K: @ n" I9 A9 N% C1 |; h$ S5、批量处理Excel表格 VC通过OLE/COM操作Excel,是通过进程间的组件技术。因此,每次读写Excel中的单元格时,都要进行进程间的切换。当数据量大,如果一个单元格一个单元格的读取,主要的时间都花费在进程切换中。因此读取多个单元格,将可有效的提高程序的运行效率。 对多个单元格的读写操作可以通过CRange中以下两个成员函数来完成。 VARIANT get_Value2(); void put_Value2(VARIANT& newValue); 其中,输入参数newValue只要输入一个二维数组,即可实现向Excel中一次写入多个单元格的值。 其中,VARIANT中实现二维数据的方法可参考 当然,在对CRange类进行操作之前,要设置CRange类对应的单元格。
- m# C x; x9 w8 ]$ S+ M6、Excel表格的保存 (1)如果要保存打开的工作簿,使用CWorkbook类的Save函数就可以保存工作簿,原文件将被覆盖。 (2)如果是新创建的工作簿,或者是要另存为,可使用CWorkbook类的SaveAs函数。 SaveAs的参数比较多。其中,第1个参数是设置要保存文件的路径;第2个参数是设置文件的格式,可在MSDN中查看枚举类型XlFileFormat来了解Excel的文件格式。经过测试,在本文所用的测试环境中,Excel2003的文件格式是xlExcel8,Excel2007的文件格式是xlExcel4。
. y5 H9 R1 Y( w3 ^7、获取当前Excel的版本 可以通过CApplication的get_Version函数来获得Excel的版本,其中,Excel2007的主版本号是12,Excel2003的主版本号是11。 ' X ^1 ~8 Y, ^; j, a- h0 q
m_LisTCtrl.SetExtendedStyle(LVS_REPORT | LVS_EX_FULLROWSELECT);
0 O3 w) ?& o' Q6 D
6 K; T! D& U6 L4 y CApplication ExcelApp;+ h) d' x7 R, _$ q1 }, H4 M$ x$ y
CWorkbooks books;
9 a' v/ M; D! ~& g* } CWorkbook book;
- ~, {9 U3 W# j' f CWorksheets sheets;& P* `* G5 S. S& }
CWorksheet sheet;
" U6 m- R4 ` [: y, ] CRange range;
7 ~3 L; B9 t7 z LPDISPATCH lpDisp = NULL;8 p) i9 x# q6 B5 ^7 r% f( G
) H5 H6 T" G2 l" w4 W //创建Excel 服务器(启动Excel)& ~2 r4 D3 H1 d
if(!ExcelApp.CreateDispatch(_T("Excel.Application"),NULL))$ @, S; ^- s& N# C
{6 z& m* b( d8 U/ k0 N6 H0 u
AfxMessageBox(_T("启动Excel服务器失败!"));5 t( u' [, z' K% j+ l
return -1;
1 |% H9 Q% Q! d4 v }
4 q- Z1 a" j4 u( N+ Z9 _5 M. `5 u) o: d4 p$ c4 [$ j1 _
/*判断当前Excel的版本*/; R0 W& c2 W: I J; R1 }
CString strExcelVersion = ExcelApp.get_Version();" |" x- U; x$ H- o1 M; G6 i
int iStart = 0;; l/ {0 p. J/ E, ]8 U
strExcelVersion = strExcelVersion.Tokenize(_T("."), iStart);4 z+ B! Q c4 S* m4 x1 P
if (_T("11") == strExcelVersion)
! S7 T- x) x2 p {
& f& t8 B7 Z" b5 o ^ AfxMessageBox(_T("当前Excel的版本是2003。"));
# E. a: I' W+ r }" t6 w& l$ m# l# V3 I
else if (_T("12") == strExcelVersion)
! D9 Q. _* U& C( f {6 p4 k. x. }$ z
AfxMessageBox(_T("当前Excel的版本是2007。"));. `! h3 |: V% S" c0 t% g8 O5 v
}
3 P" c" _9 D, u else
+ ~" L3 J- ? y* ? {
. W" i1 [( ]$ a5 M' M: i AfxMessageBox(_T("当前Excel的版本是其他版本。"));; k9 I- Y; w( t( D
}3 v! l' ~5 E9 d2 _% S& s
; S2 u ]8 h+ i4 \3 ~ ExcelApp.put_Visible(TRUE);/ ]+ n; w$ p1 {! j a* e( f; H
ExcelApp.put_UserControl(FALSE);. e2 H: w: ?( q! i9 n
- W' k7 i) I4 f /*得到工作簿容器*/7 W& n* w/ {8 H5 M
books.AttachDispatch(ExcelApp.get_Workbooks());& a8 W+ ~2 g6 c) G
* m2 y, O& s. \5 R! ]7 x" f& g# v ?
/*打开一个工作簿,如不存在,则新增一个工作簿*/
9 N& T, E3 ]( x0 T' l. i; @ CString strBookPath = _T("C:\\tmp.xls");
' ~" V' o& f0 @ W+ |- {+ X try% O/ A& [$ W+ Y% z! @2 x
{
6 g9 }2 G6 W! w. P) o /*打开一个工作簿*/
7 I1 N6 v: t+ F9 `1 b k F0 W8 q lpDisp = books.Open(strBookPath, / E/ o4 ?2 Z b4 q7 i; m# T# y
vtMissing, vtMissing, vtMissing, vtMissing, vtMissing,& M$ B" h) n9 \. \0 U0 T
vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, & J, Z7 c! q& m2 p; [
vtMissing, vtMissing, vtMissing, vtMissing);
$ P& N# `) C9 X3 c! s book.AttachDispatch(lpDisp);# Z$ I, P( o2 c8 S* J7 V0 y
}
; }! o7 X* _3 P4 L catch(...)
( r) a6 \9 ?: ?& }- o {; Q% i2 m! H6 a" x. G
/*增加一个新的工作簿*/
! i0 C) Z( M5 k. e lpDisp = books.Add(vtMissing);
& ]0 y. }0 m4 A9 B* E book.AttachDispatch(lpDisp);
6 V6 S/ e% ]) @0 r }' K z2 f8 V" m9 m$ G0 U
$ \1 i8 b0 T9 J/ |8 t6 d
1 i3 A* ?2 \, b* L# t0 V! U. b /*得到工作簿中的Sheet的容器*/
& c8 U5 B1 C! `8 L- e& u0 | sheets.AttachDispatch(book.get_Sheets());9 y( a2 E, h- g3 ]; W: [ c
2 I9 I9 | b8 A% q# C
/*打开一个Sheet,如不存在,就新增一个Sheet*// c# z8 U7 d" s+ P7 j' ^# `4 B
CString strSheetName = _T("NewSheet"); Z: b. l+ M4 ~1 {+ m6 I( {
try
' W2 u) S2 ~- ]" F4 Y' |% k {5 w$ U9 k2 ^% F2 A' O2 S Q
/*打开一个已有的Sheet*/
5 u1 i/ y3 w5 n2 E0 g" @$ C lpDisp = sheets.get_Item(_variant_t(strSheetName));
+ b5 s2 W/ G, R( N sheet.AttachDispatch(lpDisp);, J2 j4 k5 g I9 [7 w
}. Q* m- r9 Y- |, _6 ~2 ^
catch(...)0 M, g) P" B" {" E/ Z# `2 @7 a% B
{, r/ A0 ]% E) P T8 H
/*创建一个新的Sheet*/; ], N4 a8 j" T5 y3 [: `6 y! i
lpDisp = sheets.Add(vtMissing, vtMissing, _variant_t((long)1), vtMissing);
2 U6 `5 f# R7 v5 ], G" Y/ P8 | sheet.AttachDispatch(lpDisp);' p+ T' I0 |- S3 M- `0 r
sheet.put_Name(strSheetName); j! t; T% z* }. h( h0 m6 D
}
* M" T7 b! ^) c8 [4 t, R
( K) F+ F5 c. a" V$ o system("pause");
0 H: J' F4 u1 X5 j) n2 y% r# J+ `7 q+ V
/*向Sheet中写入多个单元格,规模为10*10 */$ |. Z1 b: i o7 D* H6 M
lpDisp = sheet.get_Range(_variant_t("A1"), _variant_t("J10"));
0 E5 u } r- L8 `5 Y range.AttachDispatch(lpDisp);
( ~2 e; Z6 b2 R4 J
: @! _& A0 ], z VARTYPE vt = VT_I4; /*数组元素的类型,long*/
" k( Z& x+ ^. X |8 q3 W SAFEARRAYBOUND sabWrite[2]; /*用于定义数组的维数和下标的起始值*/
. h" K/ n3 s7 y sabWrite[0].cElements = 10;
0 ~3 J, ^" ?1 j; R1 \" O( I sabWrite[0].lLbound = 0;
7 _* |2 Z3 C9 O# [) H, c2 m. o sabWrite[1].cElements = 10;
4 m5 q, v6 J& N4 w/ { sabWrite[1].lLbound = 0;' P ]7 h6 o( c( U8 o
& \# f: _9 J F! e
COleSafeArray olesaWrite;
" _- g% ?, F/ x! O9 ]2 T4 r olesaWrite.Create(vt, sizeof(sabWrite)/sizeof(SAFEARRAYBOUND), sabWrite);7 ^; Q9 n! ~- U
9 k3 h% R- d0 }2 O- x7 Q7 Z6 M7 q /*通过指向数组的指针来对二维数组的元素进行间接赋值*/
9 u0 p0 [1 I- s- X: l. R long (*pArray)[2] = NULL;2 v" h5 E. @+ G
olesaWrite.AccessData((void **)&pArray);
/ Z7 [. R7 ~7 r4 v+ { memset(pArray, 0, sabWrite[0].cElements * sabWrite[1].cElements * sizeof(long));
8 O5 _# L4 d7 q' P# A
( J1 _6 d$ `( [: l1 `0 G2 ? /*释放指向数组的指针*/3 `4 U! B: J; k- T% S
olesaWrite.UnaccessData();
5 \3 L$ L! X$ ]: P3 F" ~+ k pArray = NULL;3 [' t* K- x+ y) [: w/ y
5 d/ w0 C1 V4 w S8 L /*对二维数组的元素进行逐个赋值*/
/ e5 r/ b! {0 t2 I long index[2] = {0, 0};
- o, d% X% I2 q. o. o# J long lFirstLBound = 0;; t6 x, |9 t; L3 I# k! M
long lFirstUBound = 0;/ {% Z; t- a# Z- E# m: k! A( ]
long lSecondLBound = 0;. `' A; V, a; r' V
long lSecondUBound = 0;% G! ^/ ]% G; d7 `
olesaWrite.GetLBound(1, &lFirstLBound);- C4 z; g" d3 C1 b1 Q7 I+ I6 o
olesaWrite.GetUBound(1, &lFirstUBound);
1 ~3 P6 w7 S+ |3 f3 k olesaWrite.GetLBound(2, &lSecondLBound);
% I. F$ x. b S% t) J olesaWrite.GetUBound(2, &lSecondUBound);
& M# z& T6 y; X6 p+ z for (long i = lFirstLBound; i <= lFirstUBound; i++)
. F* m' |" P, c4 b: c& \- I4 H {
' s5 v4 V) A# W' v$ R index[0] = i;
2 @2 Q. n& u5 Q8 _" }* A for (long j = lSecondLBound; j <= lSecondUBound; j++)" X, F& B% S) _
{% N4 I5 ?8 Y- O! a% D
index[1] = j;9 h; P ]. k$ T- ^; y4 u) ?
long lElement = i * sabWrite[1].cElements + j; 7 f* `, K1 z, E @
olesaWrite.PutElement(index, &lElement);' Q) [5 s' C& G/ n3 P- v) E' H
}
) o' ~+ V( F! ]0 J }, N, c* n1 j+ |' E& }: u/ `5 u
$ j. l5 m7 C! f9 M, p% E
/*把ColesaWritefeArray变量转换为VARIANT,并写入到Excel表格中*/& ]% p9 L/ L m# H' u
VARIANT varWrite = (VARIANT)olesaWrite;
' ?% }! [ @4 `, Q; R range.put_Value2(varWrite);
8 s/ N( B# f* _6 B( ~" x' {; ~% x; G7 V7 J% F* f/ k. y/ _8 |
system("pause");0 F% r+ h3 E. m' ~# T, _
% X) f2 M, a6 P+ E- n
/*根据文件的后缀名选择保存文件的格式*/, J2 r! J. Z$ |% D* \$ U
CString strSaveAsName = _T("C:\\new.xlsx");
2 O2 T6 W( p3 J4 D9 H2 I, D CString strSuffix = strSaveAsName.Mid(strSaveAsName.ReverseFind(_T('.')));
: ]2 p8 D: N0 k XlFileFormat NewFileFormat = xlOpeNXMLWorkbook;
# E* n- n" E0 j5 X' a if (0 == strSuffix.CompareNoCase(_T(".xls")))' {/ C8 d% l5 K% [7 |: r# }$ i
{
5 A- R/ v1 t! a* T- Q0 z3 e/ v) J NewFileFormat = xlExcel8;, X7 @* D% }+ @( d) S: |5 \
}
. f, m7 y& m8 R. A book.SaveAs(_variant_t(strSaveAsName), _variant_t((long)NewFileFormat), vtMissing, vtMissing, vtMissing,
% M: r- h2 k2 `7 e0 p5 u vtMissing, 0, vtMissing, vtMissing, vtMissing, ) z ^6 |9 M3 E7 W
vtMissing, vtMissing);) P' e7 U9 z7 J2 l$ `( z* G* E
1 Q7 c3 [( b7 s2 Y; _ system("pause"); ]' T7 W. H4 f1 F5 ^7 ]& f
0 ]) {4 J9 b7 C" F
/*读取Excel表中的多个单元格的值,在listctrl中显示*/
* M0 F0 J- [( |+ q VARIANT varRead = range.get_Value2();
f) P+ q, N/ Y1 p COleSafeArray olesaRead(varRead);+ I) R9 w2 Y( u, h# w% b# v3 ^
, G4 ]- C* h% D5 q0 A- i: n% g
VARIANT varItem;
2 R8 }/ ]4 `/ ^6 x) S CString strItem;
7 S# b% e4 e" J3 x. C lFirstLBound = 0;
4 Z8 G2 ?8 d) {9 g lFirstUBound = 0;
! K b( V1 F# P! w" l+ C lSecondLBound = 0;
5 h' q1 y+ z/ f# h lSecondUBound = 0;5 j* B0 T! q0 N, ]- Z; r V
olesaRead.GetLBound(1, &lFirstLBound);% j: r; c6 w. Q( ?
olesaRead.GetUBound(1, &lFirstUBound);
; \0 f5 P( \ o+ M! E" V! W olesaRead.GetLBound(2, &lSecondLBound);
) r1 g' I; S3 [! s# [, o olesaRead.GetUBound(2, &lSecondUBound);
! Y0 N N( d, Z" E6 s& {5 L memset(index, 0, 2 * sizeof(long));& K8 Z0 ]3 {2 l: h, P% ]
m_ListCtrl.InsertColumn(0, _T(""), 0, 100);9 J5 C2 c7 F( ]' G7 B y) p4 V
for (long j = lSecondLBound; j<= lSecondUBound; j++)
* f8 g) x$ f, a3 _4 V' _6 | {) v! l+ T t( h1 @- |
CString strColName = _T("");
" ]% p7 V3 f% D; u# r strColName.Format(_T("%d"), j);
% a" c2 c: k6 h7 w% a) w3 a5 t m_ListCtrl.InsertColumn(j, strColName, 0, 100);
; [! w: G0 `8 {0 C3 l$ {9 z }
1 i: c4 S- Y! j. f Q* S for (long i = lFirstLBound; i <= lFirstUBound; i++)6 M# ~$ ~5 X5 U& Q. C$ f- t% k7 j! G
{
: L# F; n( N* |* \- j, X+ N CString strRowName = _T("");
% [# |' n8 J8 z strRowName.Format(_T("%d"), i);' k, }2 S, d" S) O2 ~0 A. u' h
m_ListCtrl.InsertItem(i-1, strRowName);( R- a8 Q4 p+ c
0 B, a2 h, P9 m7 p) d y- `# n
index[0] = i;
4 h9 F B% @! s& f+ P for (long j = lSecondLBound; j <= lSecondUBound; j++)- P' A' @ E1 q
{
. Z: c+ l# I2 }3 f- x index[1] = j;6 |0 u; F, }/ D
olesaRead.GetElement(index, &varItem);
8 g! X' Z2 n9 \0 l3 e) f3 R: M* j" i! Z* H$ [. A8 k
switch (varItem.vt)
. }8 v% a q- h* a' w3 ?# y: ?: m/ { {
7 V9 _; R ]5 ~% \ case VT_R8:- Y; i( P* U0 [$ s+ z! y+ p, G
{1 C% Z- F9 ?- u( c% w
strItem.Format(_T("%d"), (int)varItem.dblVal);0 X; m' M% K. ]
}
/ a& |) a p m! \! `0 g u. r
: u5 L' v. y' W; v% K case VT_BSTR:7 a1 s2 N4 c* s: V+ i* g% x/ |! c P
{
" e; E/ H4 E, A$ W- m strItem = varItem.bstrVal;
* q) Z5 F5 ]" n& w& h }2 m' y, D& e8 S6 o
2 U3 |& @0 U) v% h$ [: j case VT_I4:( ~7 @1 V/ S( B C
{8 a* v$ N* c+ G) ?7 E
strItem.Format(_T("%ld"), (int)varItem.lVal);
6 Q: G2 Z. m3 C }
' E! K5 @: w& ^( D0 L; o
% g% c% v2 Y% F9 k2 P4 N5 x default:: a* |$ y2 C+ f- u3 X: ?1 Y
{
% x! m& l& W0 K- d
' u: `5 h- z1 @# Q7 y9 n }3 Q) W5 h2 V) h/ O4 Y# z
}
* _: p) J3 ]3 ~: [/ y4 I5 H c `# b# v) q% `" o w
m_ListCtrl.SetItemText(i-1, j, strItem);
/ p# W4 i. {' I }, ^# f5 t) O1 B. n% R( R
}4 R/ x' C4 X) g/ Z/ Z# K
- @, y( L% X) U& [6 o) S
- Y8 K* ~" q" j) k. n6 K0 A }5 i7 e4 W$ r- m' r: J: e/ y
/*释放资源*/- c7 S+ I3 l) E( R2 d2 n
sheet.ReleaseDispatch();% U/ s. _. `) }0 G
sheets.ReleaseDispatch();+ D) W- o! y( ?* a
book.ReleaseDispatch();9 q. ?! c( D1 O( J- ~
books.ReleaseDispatch();4 B7 u% q6 z9 u! O6 g
ExcelApp.Quit();1 @1 j6 K) Y, V: f1 p0 e
ExcelApp.ReleaseDispatch();[url=][/url]6 T- `9 {) C: m; x# h
( f; N) k( e5 V |
|