这次的题目很简单,中国银行有一个查当天汇率的网页(http://www.bank-of-china.com/info/qpindex.shtml),不过是传统的Html格式,而其又没有提供Xml格式或者WebService查询。现在如果希望其他的信息系统能够随时读取其中的数据,那么方便的莫过于中行提供一个WebService接口供大家调用,这也是典型的安全的WebService应用。可惜中行没有做,那么我们能不能自己来做呢?当然可以,只要用程序分析其html网页,那么就可以很容易的读取其中的数据。文本分析,当然要看我们的"Regular Expression"(呵呵,其实这才是写这个程序的真实目的 -- 应用正则表达式。)
中行的页面类似于:
日期:2004/09/30 有效期至2004/10/07
货币名称 | 现汇买入价 | 现钞买入价 | 卖出价 | 基准价 |
---|---|---|---|---|
英镑 | 1488.1700 | 1453.1500 | 1492.6400 |
|
港币 | 105.9700 | 105.3300 | 106.2900 | 106.1100 |
美元 | 826.4200 | 821.4500 | 828.9000 | 827.6600 |
瑞士法郎 | 655.9300 | 641.1400 | 659.2200 |
|
新加坡元 | 488.7600 | 477.2600 | 490.2300 |
|
瑞典克朗 | 112.4900 | 109.8400 | 112.8300 |
|
丹麦克朗 | 136.5900 | 133.3700 | 137.0000 |
|
挪威克朗 | 121.9500 | 119.0800 | 122.3100 |
|
日元 | 7.4344 | 7.3785 | 7.4717 | 7.4519 |
加拿大元 | 650.8000 | 635.4800 | 652.7600 |
|
澳大利亚元 | 591.9900 | 578.6400 | 594.9600 |
|
欧元 | 1019.6400 | 1010.9600 | 1022.7000 | 1019.7000 |
澳门元 | 103.2200 | 102.6000 | 103.5300 |
|
菲律宾比索 | 14.6700 | 14.3300 | 14.7200 |
|
泰国铢 | 19.9000 | 19.4300 | 19.9600 |
|
新西兰元 | 553.7000 |
| 555.3600 |
|
对其代码分析后,给出了一个正则表达式,当然这个表达式还不完善,但是针对目前比较固定的中行的汇率页面来说,暂时还没有问题。
@"<tr bgcolor='#/w+' ><td height='20'>(?<currency>.*)</td>/s*" +
@"<td height='20'><p align='right'>(?<bankbuytt>/d*.?/d*)( )+.?</td>/s*" +
@"<td height='20'><p align='right'>(?<buynotes>/d*.?/d*)( )+.?</td>/s*" +
@"<td height='20'><p align='right'>(?<sell>/d*.?/d*)( )+.?</td>/s*" +
@"<td height='20'><p align='right'>(?<base>/d*.?/d*)( )+.?</td>/s*"
然后过滤就非常简单了。我一直以为代码是最好的说明,特别是对于优雅的语言来说,因为我就不多说了,代码伺候。
这是所建WebService页面ForeignExchange.asmx的代码:
using System . Collections ;
using System . ComponentModel ;
using System . Data ;
using System . Diagnostics ;
using System . Web ;
using System . Net ;
using System . Web . Services ;
using System . Xml ;
using System . Text ;
using System . Text . RegularExpressions ;
using System . IO ;
namespace ChinaBank
{
/// <summary>
/// Summary description for ForeignExchange.
/// </summary>
[ WebService ( Namespace = "http://dancefires.com/ChinaBank/" )]
public class ForeignExchange : System . Web . Services . WebService
{
public ForeignExchange ()
{
//CODEGEN: This call is required by the ASP.NET Web Services Designer
InitializeComponent ();
}
#region Component Designer generated code
//Required by the Web Services Designer
private IContainer components = null ;
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent ()
{
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose ( bool disposing )
{
if( disposing && components != null )
{
components . Dispose ();
}
base . Dispose ( disposing );
}
#endregion
[ WebMethod ]
public XmlDataDocument GetForeignExchangeRates ()
{
return getXmlDoc ();
}
[ WebMethod ]
public DataSet GetForeignExchangeRatesDataSet ()
{
return getXmlDoc (). DataSet ;
}
[ WebMethod ]
public string GetBankPage ()
{
return getWebContent ( "http://www.bank-of-china.com/info/whjrpj.html" );
}
// private methods
private string getWebContent ( string url )
{
using ( WebClient client = new WebClient () )
{
byte [] buffer = client . DownloadData ( url );
string str = Encoding . GetEncoding ( "GB2312" ). GetString ( buffer , 0 , buffer . Length );
return str ;
}
}
private XmlDataDocument getXmlDoc ()
{
string webcontent = getWebContent ( "http://www.bank-of-china.com/info/whjrpj.html" );
// Prepair for DataSet
DataSet ds = new DataSet ( "Exchange" );
DataTable dt = new DataTable ( "ForeignExchange" );
ds . Tables . Add ( dt );
dt . Columns . Add ( "Currency" , typeof (string) );
dt . Columns . Add ( "BankBuyTT" , typeof (double) );
dt . Columns . Add ( "BankBuyNotes" , typeof (double) );
dt . Columns . Add ( "BankSell" , typeof (double) );
dt . Columns . Add ( "Baseline" , typeof (double) );
XmlDataDocument xmldoc = new XmlDataDocument ( ds );
Regex expr = new Regex (
@ "<tr bgcolor='#/w+' ><td height='20'>(?<currency>.*)</td>/s*" +
@ "<td height='20'><p align='right'>(?<bankbuytt>/d*.?/d*)( )+.?</td>/s*" +
@ "<td height='20'><p align='right'>(?<buynotes>/d*.?/d*)( )+.?</td>/s*" +
@ "<td height='20'><p align='right'>(?<sell>/d*.?/d*)( )+.?</td>/s*" +
@ "<td height='20'><p align='right'>(?<base>/d*.?/d*)( )+.?</td>/s*"
, RegexOptions . Compiled );
for( Match m = expr . Match ( webcontent ) ; m . Success ; m = m . NextMatch () )
{
string key ;
DataRow row = dt . NewRow ();
row [ "Currency" ] = m . Groups [ "currency" ];
key = m . Groups [ "bankbuytt" ]. ToString ();
row [ "BankBuyTT" ] = key . Length > 0 ? Convert . ToDouble ( key )/ 100 : 0 ;
key = m . Groups [ "buynotes" ]. ToString ();
row [ "BankBuyNotes" ] = key . Length > 0 ? Convert . ToDouble ( key )/ 100 : 0 ;
key = m . Groups [ "sell" ]. ToString ();
row [ "BankSell" ] = key . Length > 0 ? Convert . ToDouble ( key )/ 100 : 0 ;
key = m . Groups [ "base" ]. ToString ();
row [ "Baseline" ] = key . Length > 0 ? Convert . ToDouble ( key )/ 100 : 0 ;
dt . Rows . Add ( row );
}
return xmldoc ;
}
}
}
客户端也很容易,只要用wsdl生成了相应的WebService Proxy后,直接调用就行了,由于我让Server端返回了DataSet,因此客户端直接用DataGrid来显示DataSet即可,非常Easy,在这个问题上客户端没有什么技术关键点。
using System . Threading ;
using System . Drawing ;
using System . Collections ;
using System . ComponentModel ;
using System . Windows . Forms ;
namespace BankDataClient
{
/// <summary>
/// Summary description for frmMainBankRates.
/// </summary>
public class frmMainBankRates : System . Windows . Forms . Form
{
private System . Windows . Forms . DataGrid dataGrid1 ;
private System . Windows . Forms . Button btnConnect ;
private System . Data . DataSet ds ;
private BankDataClient . com . dancefires .[ url ] www . ForeignExchange [/ url ] proxy = new BankDataClient . com . dancefires . www . ForeignExchange ();
private System . Windows . Forms . TextBox txtUrl ;
/// <summary>
/// Required designer variable.
/// </summary>
private System . ComponentModel . Container components = null ;
public frmMainBankRates ()
{
//
// Required for Windows Form Designer support
//
InitializeComponent ();
try
{
txtUrl . Text = System . Configuration . ConfigurationSettings . AppSettings [ "url" ];
proxy . Url = txtUrl . Text ;
}
catch ( Exception )
{
proxy . Url = "http://www.dancefires.com/ChinaBank/ForeignExchange.asmx" ;
txtUrl . Text = proxy . Url ;
}
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose ( bool disposing )
{
if( disposing )
{
if( components != null )
{
components . Dispose ();
}
}
base . Dispose ( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent ()
{
this . dataGrid1 = new System . Windows . Forms . DataGrid ();
this . ds = new System . Data . DataSet ();
this . btnConnect = new System . Windows . Forms . Button ();
this . txtUrl = new System . Windows . Forms . TextBox ();
(( System . ComponentModel . ISupportInitialize )( this . dataGrid1 )). BeginInit ();
(( System . ComponentModel . ISupportInitialize )( this . ds )). BeginInit ();
this . SuspendLayout ();
//
// dataGrid1
//
this . dataGrid1 . DataMember = "" ;
this . dataGrid1 . DataSource = this . ds ;
this . dataGrid1 . HeaderForeColor = System . Drawing . SystemColors . ControlText ;
this . dataGrid1 . Location = new System . Drawing . Point ( 32 , 48 );
this . dataGrid1 . Name = "dataGrid1" ;
this . dataGrid1 . Size = new System . Drawing . Size ( 480 , 256 );
this . dataGrid1 . TabIndex = 0 ;
//
// ds
//
this . ds . DataSetName = "Exchange" ;
this . ds . Locale = new System . Globalization . CultureInfo ( "zh-CN" );
//
// btnConnect
//
this . btnConnect . Location = new System . Drawing . Point ( 432 , 16 );
this . btnConnect . Name = "btnConnect" ;
this . btnConnect . TabIndex = 1 ;
this . btnConnect . Text = "连接" ;
this . btnConnect . Click += new System . EventHandler ( this . btnConnect_Click );
//
// txtUrl
//
this . txtUrl . Location = new System . Drawing . Point ( 32 , 16 );
this . txtUrl . Name = "txtUrl" ;
this . txtUrl . Size = new System . Drawing . Size ( 384 , 20 );
this . txtUrl . TabIndex = 2 ;
this . txtUrl . Text = "" ;
//
// frmMainBankRates
//
this . AutoScaleBaseSize = new System . Drawing . Size ( 5 , 13 );
this . ClientSize = new System . Drawing . Size ( 544 , 318 );
this . Controls . Add ( this . txtUrl );
this . Controls . Add ( this . btnConnect );
this . Controls . Add ( this . dataGrid1 );
this . Name = "frmMainBankRates" ;
this . Text = "Foreign Exchange Rates of Bank of China" ;
(( System . ComponentModel . ISupportInitialize )( this . dataGrid1 )). EndInit ();
(( System . ComponentModel . ISupportInitialize )( this . ds )). EndInit ();
this . ResumeLayout ( false );
}
#endregion
private void btnConnect_Click ( object sender , System . EventArgs e )
{
UpdateDataGrid ();
}
private void UpdateDataGrid ()
{
try
{
btnConnect . Enabled = false ;
txtUrl . ReadOnly = true ;
proxy . Url = txtUrl . Text ;
ds = proxy . GetForeignExchangeRatesDataSet ();
dataGrid1 . SetDataBinding ( ds , "ForeignExchange" );
dataGrid1 . Update ();
}
catch ( Exception err )
{
MessageBox . Show ( err . Message );
}
finally
{
txtUrl . ReadOnly = false ;
btnConnect . Enabled = true ;
}
}
[ STAThread ]
static void Main ( string [] args )
{
Application . Run ( new frmMainBankRates () );
}
}
}
有了这个例子,应该可以从中了解最基本的XML, WebService, Regular Expression, DataSet, DataGrid的知识。
软件所有代码,及相关截屏可以从下面的连接中获得:
http://www.dancefires.com/ChinaBank/