stringformulaparser.cs

来自「Excel的操作,其中可以读取及写入Excel 文件」· CS 代码 · 共 491 行

CS
491
字号
using System;
using System.Collections;

using Microsoft.Fawvw.Components.NExcel.ExcelCommon;
using Microsoft.Fawvw.Components.NExcel.Biff;

namespace Microsoft.Fawvw.Components.NExcel.Biff.Formula
{
	
	/// <summary> Parses a string formula into a parse tree</summary>
	class StringFormulaParser : Parser
	{
		/// <summary> Gets the list of lexical tokens using the generated lexical analyzer
		/// 
		/// </summary>
		/// <returns> the list of tokens
		/// </returns>
		/// <exception cref=""> FormulaException if an error occurs
		/// </exception>
		private ArrayList Tokens
		{
			get
			{
				ArrayList tokens = new ArrayList();
				
				Yylex lex = new Yylex(formula);
				lex.ExternalSheet = externalSheet;
				lex.NameTable = nameTable;
				try
				{
					ParseItem pi = lex.yylex();
					while (pi != null)
					{
						tokens.Add(pi);
						pi = lex.yylex();
					}
				}
				catch (System.IO.IOException e)
				{
					logger.warn(e.Message);
				}
				catch (System.ApplicationException e)
				{
					throw new FormulaException(FormulaException.lexicalError, formula + " at char  " + lex.Pos);
				}
				
				return tokens;
			}
			
		}
		/// <summary> Gets the formula as a string.  Uses the parse tree to do this, and
		/// does not simply return whatever string was passed in
		/// </summary>
		virtual public string Formula
		{
			get
			{
				if ((System.Object) parsedFormula == null)
				{
					System.Text.StringBuilder sb = new System.Text.StringBuilder();
					root.getString(sb);
					parsedFormula = sb.ToString();
				}
				
				return parsedFormula;
			}
			
		}
		/// <summary> Gets the bytes for the formula
		/// 
		/// </summary>
		/// <returns> the bytes in RPN
		/// </returns>
		virtual public sbyte[] Bytes
		{
			get
			{
				sbyte[] bytes = root.Bytes;
				
				if (root.isVolatile())
				{
					sbyte[] newBytes = new sbyte[bytes.Length + 4];
					Array.Copy(bytes, 0, newBytes, 4, bytes.Length);
					newBytes[0] = Token.ATTRIBUTE.Code;
					newBytes[1] = (sbyte) 0x1;
					bytes = newBytes;
				}
				
				return bytes;
			}
			
		}
		/// <summary> The logger</summary>
		private static Logger logger;
		
		/// <summary> The formula string passed to this object</summary>
		private string formula;
		
		/// <summary> The parsed formula string, as retrieved from the parse tree</summary>
		private string parsedFormula;
		
		/// <summary> The parse tree</summary>
		private ParseItem root;
		
		/// <summary> The stack argument used when parsing a function in order to
		/// pass multiple arguments back to the calling method
		/// </summary>
		private Stack arguments;
		
		/// <summary> The workbook settings</summary>
		private WorkbookSettings settings;
		
		/// <summary> A handle to the external sheet</summary>
		private ExternalSheet externalSheet;
		
		/// <summary> A handle to the name table</summary>
		private WorkbookMethods nameTable;
		
		/// <summary> Constructor</summary>
		/// <param name="">f
		/// </param>
		/// <param name="">ws
		/// </param>
		public StringFormulaParser(string f, ExternalSheet es, WorkbookMethods nt, WorkbookSettings ws)
		{
			formula = f;
			settings = ws;
			externalSheet = es;
			nameTable = nt;
		}
		
		/// <summary> Parses the list of tokens
		/// 
		/// </summary>
		/// <exception cref=""> FormulaException
		/// </exception>
		public virtual void  parse()
		{
			ArrayList tokens = Tokens;
			
			root = parseCurrent(tokens);
		}
		
		/// <summary> Recursively parses the token array.  Recursion is used in order
		/// to evaluate parentheses and function arguments
		/// 
		/// </summary>
		/// <param name="i">an iterator of tokens
		/// </param>
		/// <returns> the root node of the current parse stack
		/// </returns>
		/// <exception cref=""> FormulaException if an error occurs
		/// </exception>
		private ParseItem parseCurrent(ArrayList pis)
		{
			Stack stack = new Stack();
			Stack operators = new Stack();
			Stack args = null; // we usually don't need this
			
			bool parenthesesClosed = false;
			ParseItem lastParseItem = null;
			
			foreach(ParseItem pi in pis) 
			{
				if (parenthesesClosed) break;
			
				if (pi is Operand)
				{
					stack.Push(pi);
				}
				else if (pi is StringFunction)
				{
					handleFunction((StringFunction) pi, pis, stack);
				}
				else if (pi is Operator)
				{
					Operator op = (Operator) pi;
			
					// See if the operator is a binary or unary operator
					// It is a unary operator either if the stack is empty, or if
					// the last thing off the stack was another operator
					if (op is StringOperator)
					{
						StringOperator sop = (StringOperator) op;
						if (stack.Count<=0 || lastParseItem is Operator)
						{
							op = sop.UnaryOperator;
						}
						else
						{
							op = sop.BinaryOperator;
						}
					}
			
					if (operators.Count<=0)
					{
						// nothing much going on, so do nothing for the time being
						operators.Push(op);
					}
					else
					{
						Operator oper = (Operator) operators.Peek();
			
						// If the latest operator has a higher precedence then add to the 
						// operator stack and wait
						if (op.Precedence < oper.Precedence)
						{
							operators.Push(op);
						}
						else
						{
							// The operator is a lower precedence so we can sort out
							// some of the items on the stack
							operators.Pop(); // remove the operator from the stack
							oper.getOperands(stack);
							stack.Push(oper);
							operators.Push(op);
						}
					}
				}
				else if (pi is ArgumentSeparator)
				{
					// Clean up any remaining items on this stack
					while (operators.Count>0)
					{
						Operator o = (Operator) operators.Pop();
						o.getOperands(stack);
						stack.Push(o);
					}
			
					// Add it to the argument stack.  Create the argument stack
					// if necessary.  Items will be stored on the argument stack in
					// reverse order
					if (args == null)
					{
						args = new Stack();
					}
			
					args.Push(stack.Pop());
					// [TODO] check it
					//stack.empty();
					stack.Clear();
				}
				else if (pi is OpenParentheses)
				{
					ParseItem pi2 = parseCurrent(pis);
					Parenthesis p = new Parenthesis();
					pi2.Parent = p;
					p.add(pi2);
					stack.Push(p);
				}
				else if (pi is CloseParentheses)
				{
					parenthesesClosed = true;
				}
			
				lastParseItem = pi;
			}
			
			while (operators.Count > 0)
			{
				Operator o = (Operator) operators.Pop();
				o.getOperands(stack);
				stack.Push(o);
			}
			
			ParseItem rt = stack.Count > 0?(ParseItem) stack.Pop():null;
			
			// if the agument stack is not null, then add it to that stack
			// as well for good measure
			if (args != null && rt != null)
			{
				args.Push(rt);
			}
			
			arguments = args;
			
			if ((stack.Count > 0) || (operators.Count > 0))
			{
				logger.warn("Formula " + formula + " has a non-empty parse stack");
			}
			
			return rt;
		}
		
		/// <summary> Handles the case when parsing a string when a token is a function
		/// 
		/// </summary>
		/// <param name="sf">the string function
		/// </param>
		/// <param name="i"> the token iterator
		/// </param>
		/// <param name="stack">the parse tree stack
		/// </param>
		/// <exception cref=""> FormulaException if an error occurs
		/// </exception>
		private void  handleFunction(StringFunction sf, ArrayList pis, Stack stack)
		{
			ParseItem pi2 = parseCurrent(pis);
			
			// If the function is unknown, then throw an error
			if (sf.getFunction(settings) == Function.UNKNOWN)
			{
				throw new FormulaException(FormulaException.unrecognizedFunction);
			}
			
			// First check for possible optimized functions and possible
			// use of the Attribute token
			if (sf.getFunction(settings) == Function.SUM && arguments == null)
			{
				// this is handled by an attribute
				Attribute a = new Attribute(sf, settings);
				a.add(pi2);
				stack.Push(a);
				return ;
			}
			
			if (sf.getFunction(settings) == Function.IF)
			{
				// this is handled by an attribute
				Attribute a = new Attribute(sf, settings);
				
				// Add in the if conditions as a var arg function in
				// the correct order
				VariableArgFunction vaf = new VariableArgFunction(settings);
				
				// [TODO] TEST is the order the same as in Java?
				object[] argumentsArray = arguments.ToArray();
				for (int j = 0 ; j < argumentsArray.Length ; j++)
				{
				vaf.add((ParseItem) argumentsArray[j]);
				}
				
				
				a.IfConditions = vaf;
				stack.Push(a);
				return ;
			}
			
			// Function cannot be optimized.  See if it is a variable argument 
			// function or not
			if (sf.getFunction(settings).NumArgs == 0xff)
			{
				// If the arg stack has not been initialized, it means
				// that there was only one argument, which is the
				// returned parse item
				if (arguments == null)
				{
					VariableArgFunction vaf = new VariableArgFunction(sf.getFunction(settings), 1, settings);
					vaf.add(pi2);
					stack.Push(vaf);
				}
				else
				{
					// Add the args to the function in reverse order.  The 
					// VariableArgFunction will reverse these when it writes out the 
					// byte version as they are stored in the correct order
					// within Excel
					int numargs = arguments.Count;
					VariableArgFunction vaf = new VariableArgFunction(sf.getFunction(settings), numargs, settings);
					
					for (int j = 0; j < numargs; j++)
					{
						ParseItem pi3 = (ParseItem) arguments.Pop();
						vaf.add(pi3);
					}
					stack.Push(vaf);
					// [TODO] - check it
					//		arguments.empty();
					arguments.Clear();
					arguments = null;
				}
				return ;
			}
			
			// Function is a standard built in function
			BuiltInFunction bif = new BuiltInFunction(sf.getFunction(settings), settings);
			
			int numargs2 = sf.getFunction(settings).NumArgs;
			if (numargs2 == 1)
			{
				// only one item which is the returned ParseItem
				bif.add(pi2);
			}
			else
			{
				if ((arguments == null && numargs2 != 0) || (arguments != null && numargs2 != arguments.Count))
				{
					throw new FormulaException(FormulaException.incorrectArguments);
				}
				// multiple arguments so go to the arguments stack.  
				// Unlike the variable argument function, the args are
				// stored in reverse order
				// [TODO] TEST is the order the same as in Java?
				object[] argumentsArray = arguments.ToArray();
				for (int j = 0 ; j < numargs2 ; j++)
				{
				bif.add((ParseItem) argumentsArray[j]);
				}
				
			}
			stack.Push(bif);
		}
		
		/// <summary> Default behaviour is to do nothing
		/// 
		/// </summary>
		/// <param name="colAdjust">the amount to add on to each relative cell reference
		/// </param>
		/// <param name="rowAdjust">the amount to add on to each relative row reference
		/// </param>
		public virtual void  adjustRelativeCellReferences(int colAdjust, int rowAdjust)
		{
			root.adjustRelativeCellReferences(colAdjust, rowAdjust);
		}
		
		/// <summary> Called when a column is inserted on the specified sheet.  Tells
		/// the formula  parser to update all of its cell references beyond this
		/// column
		/// 
		/// </summary>
		/// <param name="sheetIndex">the sheet on which the column was inserted
		/// </param>
		/// <param name="col">the column number which was inserted
		/// </param>
		/// <param name="currentSheet">TRUE if this formula is on the sheet in which the
		/// column was inserted, FALSE otherwise
		/// </param>
		public virtual void  columnInserted(int sheetIndex, int col, bool currentSheet)
		{
			root.columnInserted(sheetIndex, col, currentSheet);
		}
		
		
		/// <summary> Called when a column is inserted on the specified sheet.  Tells
		/// the formula  parser to update all of its cell references beyond this
		/// column
		/// 
		/// </summary>
		/// <param name="sheetIndex">the sheet on which the column was removed
		/// </param>
		/// <param name="col">the column number which was removed
		/// </param>
		/// <param name="currentSheet">TRUE if this formula is on the sheet in which the
		/// column was inserted, FALSE otherwise
		/// </param>
		public virtual void  columnRemoved(int sheetIndex, int col, bool currentSheet)
		{
			root.columnRemoved(sheetIndex, col, currentSheet);
		}
		
		/// <summary> Called when a column is inserted on the specified sheet.  Tells
		/// the formula  parser to update all of its cell references beyond this
		/// column
		/// 
		/// </summary>
		/// <param name="sheetIndex">the sheet on which the column was inserted
		/// </param>
		/// <param name="row">the column number which was inserted
		/// </param>
		/// <param name="currentSheet">TRUE if this formula is on the sheet in which the
		/// column was inserted, FALSE otherwise
		/// </param>
		public virtual void  rowInserted(int sheetIndex, int row, bool currentSheet)
		{
			root.rowInserted(sheetIndex, row, currentSheet);
		}
		
		/// <summary> Called when a column is inserted on the specified sheet.  Tells
		/// the formula  parser to update all of its cell references beyond this
		/// column
		/// 
		/// </summary>
		/// <param name="sheetIndex">the sheet on which the column was removed
		/// </param>
		/// <param name="row">the column number which was removed
		/// </param>
		/// <param name="currentSheet">TRUE if this formula is on the sheet in which the
		/// column was inserted, FALSE otherwise
		/// </param>
		public virtual void  rowRemoved(int sheetIndex, int row, bool currentSheet)
		{
			root.rowRemoved(sheetIndex, row, currentSheet);
		}
		static StringFormulaParser()
		{
			logger = Logger.getLogger(typeof(StringFormulaParser));
		}
	}
}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?