Cell公式解析(1)

Cell产品背景:

Cell组件、插件是中国第一个商业控件,采用ActiveX技术,能够快速制作出专业的各种复杂的中国报表,拥有600多个编程接口,实现报表自定义,报表显示、打印预览、打印,图表,公式,自定义函数、资源本地化等强大功能,能够完全读写Excel档。Cell完全解决了excel在使用的过程中出现的制表不稳定、程序运行慢且效率低、经常出现莫名其妙的错误以及要求客户机器上必须安装excel等等问题。

 

典型功能
¨制作中国式的复杂报表
¨丰富的单元格式与设计
¨强大的打印及打印预览
¨别具一格的选择接口
¨强大的图表功能
¨强大的单元公式
¨自定义函数
¨与Excel文件格式的兼容
¨名称管理(单元格命名, 数字、字符串、公式、区域命名),
¨资源本地化
¨转换PDFCSV格式文件

Cell公式:

           公式的解析运算, 对一个表格控件来说, 是必不可少的一个部分. 在数据汇总, 周报年报总结等多种应用环境. 但是, 表格控件有其特殊的地方, 表格是以一个一个的格式构成的, 没有公式都是有自己的一个位置的. 每个单元格公式都是跟其他单元格有相互引用, 跨表页引用等多种情况.

           Cell根据表格控件的各种特性, 自主研发了适合表格控件的一套公式解析计算引擎. 如图:

 

 

这里, 我将分几篇文章, 详细介绍一下Cell是如何实现上图中的各个部分.

Cell公式解析:

           如何对单元格设置一个公式, 相信每个人都很熟悉Excel的应用, Cell本身的模板设计器, Excel有很多的共同之处, 你只需要在Cell单元格上按一下 “=” , Cell 的公式编辑器就会显示在Cell 的顶端(类似Excel的公式栏), 选中 “B2” 单元格, ”=”, 其中输入”A1”, 引用单元格A1的内容. 这样, B2 就有一个公式了, 在修改A1单元格内容的时候, B2 的值就会改变.

 

我们也可以通过接口的方式来设置公式, 可以调用Cell接口SetFormula, 详细用法请参考http://www.cellsoft.cc/cell/index.asp

 

Cell对公式是如何解析的呢??

在输入公式的时候, 经过词法分析, Cell将输入的公式字符串解析为中间代码, 存储在SG_Formula, SG_EXPR, SG_POLISH_UNIT 结构中.

struct SG_Formula

{

        long       col;

        long       row;

        long       page;

        CString     text; //公式字符串

        long       state;

        SG_EXPR   expr;

};

 

struct  SG_EXPR

{

        SG_ENUM_VALUE  valtype;     

        USHORT          polishnum;

        SG_POLISH_UNIT  *polishs;

};

 

struct  SG_POLISH_UNIT

{

        SG_ENUM_POLITYPE etype;

        short             startpos;

        short             endpos;

        union

{

                 double           f;                         // value for a numerical constant

                 LPTSTR                    s;                        // a string , may occur is logic expr

                 SG_ENUM_OPERATOR  opr;                 // inner code for an operator */

                 SG_AREA            area;                  // an area 

                 SG_3DCell           cell;                  // a cell

                 SG_FUNCCALL       *funcCall;              // a function call

        };

};

 

SG_POLISH_UNIT: 存储一个基本的语法单位. SG_ENUM_POLITYPE标示此语法单位的类型

SG_EXPR: 存储一个解析后的公式, 由多个SG_POLISH_UNIT组成

SG_Formula: 存储公式整个公式的信息, 包括位置, 公式解析后的 SG_EXPR

 

解析后的公式在cell中的都是存储在一个SG_Formula结构中.

我通过一个例子, 来看看Cell如何解析一个公式的.

 

例如 输入这样的一个公式 B2=A1+1+2

 

词法分析:

词法分析就是从左到右逐个字符的对公式进行扫描, 分析出其中单词符号.Cell中的单词符号的种类分为 6 , 对应于SG_POLISH_UNIT中的 6 中类型, :

 

Double: 数值类型

String : 字符串类型

SG_ENUM_OPERATOR : 操作运算符类型 ( : “+”, “-“, “and” “:”, “>” )

SG_AREA: 单元格区域类型 (: A1:B2, B3:B4)

SG_3DCell: 单个单元格类型 (: A1)

SG_FUNCCALL: 函数类型

 

对于我们例子输入的公式来说, 我们可以将公式分解为下面5个单词符号

 

“A1” -> SG_3DCel类型

“+” ->  SG_ENUM_OPERATOR 操作符

“1” , ”2” -> Double 数值类型

 

Cell对公式扫描过程中, 最终也是生成这样的一个单词符号组合. 解析过程中,

按照  以字符开头的符号 à 数字符号 à  字符串 à 其他符号 的过程解析的,

下面是Cell获得单词符号的代码.

 

SG_ENUM_TOKEN GetToken( )

{

        USES_CONVERSION;

        LPCTSTR curstr = sourcecode +  curpos;

        long realstartpos;

 

        if( curtoken == TOKEN_CODE_END ){

                return TOKEN_CODE_END;

        }

 

        //去掉空格

        while ( ( unsigned ) *curstr <= 32 && *curstr  != _T('/n') && *curstr  != 0 )

                ++curstr;

 

        //去掉双斜杠注释

        if( *curstr == _T('/') && *(curstr+1) == _T('/') )

        {

                curstr += 2;

                while( TRUE )

                {

                        if( *curstr == 0x0d && *(curstr+1) == 0x0a)

                        {

                                curstr += 2;

                                break;

                        }

                        else if( *curstr == 0 )break;

                        else curstr++;

                }

        }

 

        realstartpos = curstr - sourcecode;

        lastpos = realstartpos;

 

        //=========================   any token begin with alpha or chinese

        if ( (*curstr&0x80) || _istalpha( *curstr ) || *curstr == _T('$')) {

                while( _istalnum( *curstr ) || *curstr == '_' || *curstr & 0x80 || *curstr == _T('$')){

                         //*curstr = tolower( *curstr );

                         ++curstr;

                }

                CopyTokenString( realstartpos, curstr-sourcecode);

                if( (curstr-sourcecode) - realstartpos+1 > SG_MAX_TOKEN_LEN)

                        throw ERR_TOKEN_TOOLONG;

 

                if ( m_Func->IsFunction( curtokenstr ) )

                        curtoken = TOKEN_FUNC;

                else if( _tcsicmp( curtokenstr, _T("and") ) == 0 )

                        curtoken = TOKEN_AND;

                else if( _tcsicmp( curtokenstr, _T("or") ) == 0 )

                        curtoken = TOKEN_OR;

                else if( _tcsicmp( curtokenstr, _T("not") ) == 0 )

                        curtoken = TOKEN_NOT;

                else

                        curtoken = TOKEN_ID;

        }

 

        //========================= =  digit token: include integer and float number

    else if ( _istdigit( *curstr ) || *curstr == _T('.') ) {

                if( _istdigit( *curstr )){

                        curtoken = TOKEN_INTEGER;    

                        while( _istdigit( *++curstr ) );

                }

 

                if( *curstr == '.' ){

                        while( _istdigit( *++curstr ) );

                        curtoken = TOKEN_FLOAT_NUM;

                }

 

                if ( *curstr == 'e' || *curstr == 'E' ){

                        if ( curstr[1] == '+' || curstr[1] == '-' ){

                                if ( !_istdigit( curstr[2] ) )

                                        throw ERR_EXPNT_EXPECTED;

                                ++curstr;

                        }

                        if ( _istdigit( curstr[1] ) ){

                                while ( _istdigit( *++curstr ) );

                                curtoken = TOKEN_FLOAT_NUM;

                        }

                }

 

                //要求其后面不是!

                BOOL numvalid = TRUE;

                LPCTSTR tstr = curstr;

                while( *tstr != 0 ){

                        if( *tstr == 32 ){ tstr++; continue; }

                        else if( *tstr == '!' ){

                                numvalid = FALSE;

                                break;

                        }

                        else break;

                }

 

                if( numvalid ){

                        CopyTokenString( realstartpos, curstr-sourcecode );

                        if ( curtoken == TOKEN_INTEGER ){

                                if ( _tcslen( curtokenstr ) > 10 || ( _tcslen( curtokenstr ) == 10 ){

                                        curtoken = TOKEN_FLOAT_NUM;

                                        floattokenval = ttof( curtokenstr );

                                }

                                else

                                        inttokenval = _ttol ( curtokenstr );

                        }

                        else

                        {

                                floattokenval = ttof( curtokenstr );

                        }

                }

                else{        //这是一个页签

                        CopyTokenString( realstartpos, curstr-sourcecode);

                        curtoken = TOKEN_ID;

                }

        }

 

        //==============================        string token: like "Avsd" 

        else if ( *curstr == '"' ) {

                do {

                        ++curstr;

                }while( !(*curstr  == '"' && *(curstr-1) != '//' ) && *curstr );

 

                if ( *curstr == 0 )

                        throw ERR_YIN_HAO_UNMATCH;

                //CopyTokenString( realstartpos+1, curstr-sourcecode );

                strtokenval = sourcecode+realstartpos+1;

                strtokenlen = (curstr-sourcecode)-realstartpos-1;

                ++curstr;

                curtoken = TOKEN_STRING;

         }

 

 

        //=================================  any other token

        else {

                switch( *curstr ){

                        case  ':':   

                                curtoken = TOKEN_MAO_HAO;

                                break;

                        case  '/n':

                                curtokenstr[0] = 0;

                                curtoken = TOKEN_LINE_END;

                                break;

                   case '/0':

                           curtokenstr[0] = 0;

                           curtoken = TOKEN_CODE_END;

                           break;

                   case  ',':

                            curtoken = TOKEN_DOU_HAO;

                                break;

                        case  '!':

                            curtoken = TOKEN_TANG_HAO;

                                break;

           case '/' :

                                curtoken = TOKEN_DIV_OP;

                                break;

                   case '-':

                                curtoken = TOKEN_MINUS_OP;

                           break;

                   case '+':

                                curtoken = TOKEN_ADD_OP;

                                break;

                   case '*':

                           curtoken = TOKEN_MULTI_OP;

                           break;

                   case '(':

                           curtoken = TOKEN_L_KUO_HAO;

                           break;

                        case ')':

                                  curtoken = TOKEN_R_KUO_HAO;

                                  break;

                        case '=':

                                  curtoken = TOKEN_EQUAL_HAO;

                                  break;

                    case '>':

                                if ( *(curstr+1) == '=' ){

                                        curstr++;

                                        curtoken = TOKEN_EQGREAT_HAO;

                                }

                                else

                                        curtoken = TOKEN_GREAT_HAO;

                                break;

                        case '<':

                                if ( *(curstr+1) == '=' ){

                                        curstr++;

                                        curtoken = TOKEN_EQSMALL_HAO;

                                }

                                else if ( *(curstr+1) == '>' ){

                                        curstr++;

                                        curtoken = TOKEN_NOTEQ_HAO;

                                }

                                else

                                        curtoken = TOKEN_SMALL_HAO;

                                break;

                        case '^':

                                  curtoken = TOKEN_POWER;

                                  break;

                        default :

                                curtoken = TOKEN_UNKNOWN;

                }

                curtokenstr[0] = *curstr++;

                curtokenstr[1] = '/0';

        }

 

        curpos = curstr - sourcecode;

        tokenstartpos = realstartpos;

        return curtoken;

}

 

中间代码:

Cell通过词法分析, 会将公式 A1+1+2 分解为 5 个基本的语法单位. 每一个语法单位存储在SG_POLISH_UNIT.  5个语法单位构成一个公式SG_EXPR.

         

A1+1+2 分解后, 每一单位都会分配一个Cell可以识别的单词符号, “A1” 识别为一个T_CELL类型的符号, “+” 识别为一个 O_ADD 操作运算符号等

 

每一个语法单位通过 startpos endpos 对应到你输入的源公式字符串. 方便以后对公式做调整.

 

分解后的5个语法单位以数组的形式存储在公式SG_EXPR 中的 polishs 成员中.

 

这样, 输入的公式经过词法分析后, 我们得到了 语法单位的 类型, 单词符号, 还有每个语法单位对应的一个Cell内部类型对象. 为随后的合法性验证, 公式计算的打下基础.

         
 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值