// MODIFIED PROTECTION FOR PS5 /// <summary> /// If changing the contents of the named cell to be the formula would cause a /// circular dependency, throws a CircularException, and no change is made to the spreadsheet. /// /// Otherwise, the contents of the named cell becomes formula. The method returns a /// list consisting of name plus the names of all other cells whose value depends, /// directly or indirectly, on the named cell. The order of the list should be any /// order such that if cells are re-evaluated in that order, their dependencies /// are satisfied by the time they are evaluated. /// /// For example, if name is A1, B1 contains A1*2, and C1 contains B1+A1, the /// list {A1, B1, C1} is returned. /// </summary> protected abstract IList <String> SetCellContents(String name, Formula formula);
/// <summary> /// If the formula parameter is null, throws an ArgumentNullException. /// /// Otherwise, if name is null or invalid, throws an InvalidNameException. /// /// Otherwise, if changing the contents of the named cell to be the formula would cause a /// circular dependency, throws a CircularException, and no change is made to the spreadsheet. /// /// Otherwise, the contents of the named cell becomes formula. The method returns a /// list consisting of name plus the names of all other cells whose value depends, /// directly or indirectly, on the named cell. /// /// For example, if name is A1, B1 contains A1*2, and C1 contains B1+A1, the /// list {A1, B1, C1} is returned. /// </summary> protected override IList <string> SetCellContents(string name, Formula formula) { //Normalizes the name of the Cell String normalizedName = Normalize(name); //Creates a new Cell passing the name and formula Cell c = new Cell(normalizedName, formula); //Sets the contents of the cell with the formula c.SetContents(formula); //Sets the value of the Cell which should be a double if the formula is valid //or a FormulaError if the formula is invalid c.SetValue(Lookup); //A boolean to keep track if a new Entry is made bool newEntry = false; //Gets the previous dependees of the cell before HashSet <string> dependees = new HashSet <string>(); //A Cell to store the prevCell Cell prevCell = null; //If the dictionary contains the key name, then goes here if (dictionary.ContainsKey(normalizedName)) { //Gets the prevCell prevCell = dictionary[normalizedName]; //Replaces it with the new cell dictionary[normalizedName] = c; //Gets the prevDependees of the graph dependees = new HashSet <string>(graph.GetDependees(normalizedName)); //Replaces the dependees with the variables of the formula graph.ReplaceDependees(normalizedName, formula.GetVariables()); } else { //Adds the cell to the dictionary dictionary.Add(normalizedName, c); //Gets the prevDependees of the graph dependees = new HashSet <string>(graph.GetDependees(normalizedName)); //Replaces the dependees with the variables of the formula graph.ReplaceDependees(normalizedName, formula.GetVariables()); //Sets the newEntry boolean to true newEntry = true; } //Creates a new list to be returned List <string> recalculatedList; try { //Gets the orders of the cell recalculated and stores it into a list recalculatedList = new List <string>(GetCellsToRecalculate(normalizedName)); //Recalculates the values of all the cells RecalculateValues(recalculatedList); } //Goes in here if we encounter a CircularException catch (CircularException ce) { //If a new Cell was added, we remove it from the dictionary if (newEntry == true) { dictionary.Remove(normalizedName); } //Otherwise, we revert the value set to the value before in the Cell else { dictionary[normalizedName] = prevCell; } //Replace the dependees of the name to it's original form graph.ReplaceDependees(normalizedName, dependees); //Throws a CircularException throw new CircularException(); } //Sets changed to true Changed = true; //Returns the list of cells recalculated return(recalculatedList); }
/// <summary> /// Requires that all of the variables in formula are valid cell names. /// /// If name is null or invalid, throws an InvalidNameException. /// /// Otherwise, if changing the contents of the named cell to be the formula would cause a /// circular dependency, throws a CircularException. /// /// Otherwise, the contents of the named cell becomes formula. The method returns a /// Set consisting of name plus the names of all other cells whose value depends, /// directly or indirectly, on the named cell. /// /// For example, if name is A1, B1 contains A1*2, and C1 contains B1+A1, the /// set {A1, B1, C1} is returned. /// </summary> protected override ISet <string> SetCellContents(string name, Formula formula) { name = name.ToUpper(); // check if passed in formula contains a dependent of the named cell // to prevent a ciruclar dependency //foreach(string token in GetDirectDependents(name)) //{ // if(formula.ToString().Contains(token)) // { // throw new CircularException(); // } //} HashSet <string> set = new HashSet <string>(); // A backup spreadsheet for revert in case a CircularException is thrown Dictionary <string, Cell> tempDict = new Dictionary <string, Cell>(); // A backup DependencyGraph for revert in case a CircularException is thrown DependencyGraph tempGraph = new DependencyGraph(graph); //create a copy for the spreadsheet foreach (KeyValuePair <string, Cell> key in cellList) { tempDict.Add(key.Key, key.Value); } // Replace current cell's content for update graph.ReplaceDependees(name, new List <string>()); // Create dependency relationship between cell and valid variables generated by Formula foreach (string var in formula.GetVariables()) { CheckParameters(var); graph.AddDependency(var, name); } // Generate a new cell if such named cell doesn't exists yet, or simply replace the existing // cell with new information GenerateCellInfo(name, formula); set.Add(name); // This try catch block is used to detect possible Circular dependency by calling GetCellsToRecalculate() try { foreach (string var in GetCellsToRecalculate(name)) { set.Add(var); } } catch (CircularException e) { // If a CircularException is caught, revert the entire operation. cellList = tempDict; graph = tempGraph; Changed = false; throw e; } // Update current named cell's dependee's content, value and dependency UpdateCellInfo(set, name); Changed = true; return(set); }
/// <summary> /// /// Creates a Spreadsheet that is a duplicate of the spreadsheet saved in source. /// /// See the AbstractSpreadsheet.Save method and Spreadsheet.xsd for the file format /// specification. /// /// If there's a problem reading source, throws an IOException. /// /// Else if the contents of source are not consistent with the schema in Spreadsheet.xsd, /// throws a SpreadsheetReadException. /// /// Else if the IsValid string contained in source is not a valid C# regular expression, throws /// a SpreadsheetReadException. (If the exception is not thrown, this regex is referred to /// below as oldIsValid.) /// /// Else if there is a duplicate cell name in the source, throws a SpreadsheetReadException. /// (Two cell names are duplicates if they are identical after being converted to upper case.) /// /// Else if there is an invalid cell name or an invalid formula in the source, throws a /// SpreadsheetReadException. (Use oldIsValid in place of IsValid in the definition of /// cell name validity.) /// /// Else if there is an invalid cell name or an invalid formula in the source, throws a /// SpreadsheetVersionException. (Use newIsValid in place of IsValid in the definition of /// cell name validity.) /// /// Else if there's a formula that causes a circular dependency, throws a SpreadsheetReadException. /// /// Else, create a Spreadsheet that is a duplicate of the one encoded in source except that /// the new Spreadsheet's IsValid regular expression should be newIsValid. /// </summary> /// <param name="source"></param> /// <param name="newIsValid"></param> public Spreadsheet(TextReader source, Regex newIsValid) { Changed = false; // Create an XmlSchemaSet object. XmlSchemaSet sc = new XmlSchemaSet(); string name; string contents; // a regex placeholder for regex value embedded inside of source file Regex oldIsValid = null; // a regex to check cell name Regex formatCheck = new Regex(@"^[a-zA-Z][a-zA-Z]*[1-9][0-9]*$"); // NOTE: To read states3.xsd this way, it must be stored in the same folder with the // executable. To arrange this, I set the "Copy to Output Directory" propery of states3.xsd to // "Copy If Newer", which will copy states3.xsd as part of each build (if it has changed // since the last build). sc.Add(null, "Spreadsheet.xsd"); // Configure validation XmlReaderSettings settings = new XmlReaderSettings { ValidationType = ValidationType.Schema, Schemas = sc }; // if an error occurs while reading the source, ValidationCallback will be triggered settings.ValidationEventHandler += ValidationCallback; using (XmlReader reader = XmlReader.Create(source, settings)) { while (reader.Read()) { if (reader.IsStartElement()) { // check if a spreadsheet tag exists if (reader.Name.Equals("spreadsheet")) { // check if the regex embedded is a valid C# regex if (CheckRegex(reader["IsValid"])) { oldIsValid = new Regex(reader["IsValid"]); } else { throw new SpreadsheetReadException("Invalid C# regular expression"); } } // begin to assign name and contents into current Spreadsheet if (reader.Name.Equals("cell")) { name = reader["name"]; contents = reader["contents"]; if (cellList.Keys.Contains(name.ToUpper())) { throw new SpreadsheetReadException("A duplicated named cell detected"); } // check validity for name using oldIsValid if (!formatCheck.IsMatch(name) || !oldIsValid.IsMatch(name)) { throw new SpreadsheetReadException("Invalid cell name in the source."); } // check validity for name using newIsValid if (!formatCheck.IsMatch(name) || !newIsValid.IsMatch(name)) { throw new SpreadsheetVersionException("Invalid cell name in the source."); } // if contents contains a formula, do the following checks if (contents.StartsWith("=")) { string temp = contents.Remove(0, 1); try { // check validity for formula using oldIsValid by imposing extra regex Formula f = new Formula(temp, s => s.ToUpper(), s => oldIsValid.IsMatch(s)); } catch { throw new SpreadsheetReadException("Invalid formula in the source."); } try { // check validity for formula using newIsValid by imposing extra regex Formula f = new Formula(temp, s => s.ToUpper(), s => newIsValid.IsMatch(s)); } catch { throw new SpreadsheetVersionException("Invalid formula in the source."); } } // if all data are valid, prepare to set cell contents IsValid = newIsValid; SetContentsOfCell(name, contents); } } // reset temporary strings name = ""; contents = ""; } } }
public override ISet <string> SetContentsOfCell(string name, string content) { // Check to see if content is null if (content == null) { throw new ArgumentNullException(); } // Check to see if name is null if (name == null) { throw new InvalidNameException(); } // Normalize name String normalName = name.ToUpper(); // Check to see if name is valid if (NameIsValid(normalName)) { // If content parses as a double double parsedContent; if (double.TryParse(content, out parsedContent)) { return(SetCellContents(normalName, parsedContent)); } // If content begins with an = sign String equalPattern = @"^="; if (Regex.IsMatch(content, equalPattern)) { String contentToBeParsed = content.Substring(1).ToUpper(); // Try to parse into formula Formula parsedFormula; try { parsedFormula = new Formula(contentToBeParsed); } catch (FormulaFormatException e) { throw e; } // Try to set contents of name to be formula, throw CircularException if needed try { return(SetCellContents(normalName, parsedFormula)); } catch (CircularException e) { throw e; } } // Content should just be a string since it isn't a formula or double else { return(SetCellContents(normalName, content)); } } else { throw new InvalidNameException(); } }
protected override ISet <string> SetCellContents(string name, Formula formula) { // Parse the formula to get the variables IEnumerable variables = formula.GetVariables(); // Get features of cell prior to removing it in case there is a circular dependency Cell beforeCircleCheck = new Cell(); Object contents = new Object(); if (spreadsheetCells.TryGetValue(name, out beforeCircleCheck)) { contents = beforeCircleCheck.cellContents; } // If the cell already exists, remove it if (spreadsheetCells.ContainsKey(name)) { spreadsheetCells.Remove(name); foreach (String variable in variables) { dependencies.RemoveDependency(name, variable); } } // Create new cell object Cell toBeAdded = new Cell(); // Set cellName equal to name toBeAdded.cellName = name; // Set cellContents eequal to formula toBeAdded.cellContents = "=" + formula; // Set cellContentsType equal to formula toBeAdded.cellContentsType = "formula"; // Add (name, cell) to spreadsheetCells dictionary spreadsheetCells.Add(name, toBeAdded); // Add to dependency Graph foreach (String variable in variables) { dependencies.AddDependency(name, variable); } // Check for circular dependencies. If one is found, restore origional cell try { GetCellsToRecalculate(name); } catch (CircularException e) { // Restore origional cell if (!(beforeCircleCheck == null)) { spreadsheetCells.Remove(name); foreach (String var in variables) { dependencies.AddDependency(name, var); } toBeAdded.cellContents = beforeCircleCheck.cellContents; spreadsheetCells.Add(name, toBeAdded); } throw e; } // Set value of cell try { toBeAdded.cellValue = formula.Evaluate((x) => (double)GetCellValue(x)); } catch (InvalidCastException) { toBeAdded.cellValue = new FormulaError(); } catch (FormulaEvaluationException) { toBeAdded.cellValue = new FormulaError(); } // Get dependencies for return method call HashSet <String> returnValues = new HashSet <String>(); // Call GetCellsToRecalculate for direct dependents IEnumerable dependents = GetCellsToRecalculate(name); foreach (String temp in dependents) { // Add direct dependents to returnValues returnValues.Add(temp); } // Call GetDependees on dependencies to get indirect dependents IEnumerable dependees = dependencies.GetDependees(name); foreach (String temp in dependees) { // Add indriect dependents to returnValues returnValues.Add(temp); } // Recalculate Cell recalculation; foreach (String depend in dependents) { if (spreadsheetCells.TryGetValue(depend, out recalculation)) { // Set value of cell try { recalculation.cellValue = formula.Evaluate((x) => (double)GetCellValue(x)); } catch (InvalidCastException) { recalculation.cellValue = new FormulaError(); } catch (FormulaEvaluationException) { recalculation.cellValue = new FormulaError(); } } } // Change changed to true since we made a change to the spreadsheet Changed = true; // return return(returnValues); }
protected override ISet <string> SetCellContents(string name, string text) { // Check to see if text is empty if (text.Equals("")) { Cell value; if (spreadsheetCells.TryGetValue(name, out value)) { value.cellContents = ""; value.cellValue = ""; // Readd cell's that need to be recomputed IEnumerable <String> recalc = GetCellsToRecalculate(name); //recalc.Skip<String>(1); Cell contents; foreach (String cellNm in recalc) { spreadsheetCells.TryGetValue(cellNm, out contents); if (contents.cellName.Equals(name)) { continue; } SetContentsOfCell(cellNm, contents.cellContents.ToString()); String toBeAddedContents = contents.cellContents.ToString(); } } spreadsheetCells.Remove(name); return(new HashSet <String>()); } // If the cell already exists, remove it if (spreadsheetCells.ContainsKey(name)) { spreadsheetCells.Remove(name); } // Create new cell object Cell toBeAdded = new Cell(); // Set cellName equal to name toBeAdded.cellName = name; // Set cellContents equal to text toBeAdded.cellContents = text; // Set cellValue equal to text toBeAdded.cellValue = text; // Set cellContentsType equal to string toBeAdded.cellContentsType = "string"; // Add (name, cell) to spreadsheetCells dictionary spreadsheetCells.Add(name, toBeAdded); // Get dependencies for return method call HashSet <String> returnValues = new HashSet <String>(); // Call GetCellsToRecalculate to get direct dependents IEnumerable dependents = GetCellsToRecalculate(name); foreach (String temp in dependents) { // Store direct dependents in returnValues returnValues.Add(temp); } // Call GetDependees on dependencies to get indirect dependents IEnumerable dependees = dependencies.GetDependees(name); foreach (String temp in dependees) { // Store indirect dependents in returnValues returnValues.Add(temp); } Cell recalculation; foreach (String depend in dependents) { if (spreadsheetCells.TryGetValue(depend, out recalculation)) { // Set value of cell try { String equalPattern = @"^="; String cellContents = recalculation.cellContents.ToString(); if (Regex.IsMatch(cellContents, equalPattern)) { String parsedContents = cellContents.Substring(1); Formula f = new Formula(parsedContents); recalculation.cellValue = f.Evaluate((x) => (double)GetCellValue(x)); //SetContentsOfCell(recalculation.cellName, recalculation.cellContents.ToString()); } } catch (InvalidCastException) { recalculation.cellValue = new FormulaError(); } catch (FormulaEvaluationException) { recalculation.cellValue = new FormulaError(); } } } // Change changed to true since we made a change to the spreadsheet Changed = true; // return return(returnValues); }
protected override ISet <string> SetCellContents(string name, double number) { // If the cell already exists, remove it if (spreadsheetCells.ContainsKey(name)) { spreadsheetCells.Remove(name); } // Create new cell Cell toBeAdded = new Cell(); // Set cellName equal to name toBeAdded.cellName = name; // Set cellContents equal to number toBeAdded.cellContents = number; // Set cellValue equal to number toBeAdded.cellValue = number; // Set cellContentsType equal to double toBeAdded.cellContentsType = "double"; // Add (name, cell) to our spreadsheetCells dictionary spreadsheetCells.Add(name, toBeAdded); // Get dependecies for return portion of method call HashSet <String> returnValues = new HashSet <String>(); // Call GetCellsToRecalculate to get direct dependents IEnumerable dependents = GetCellsToRecalculate(name); foreach (String temp in dependents) { // Add direct dependents to returnValues returnValues.Add(temp); } // Call GetDependees on dependencies to get indirect dependents IEnumerable dependees = dependencies.GetDependees(name); foreach (String temp in dependees) { // Add indirect dependents to returnValues returnValues.Add(temp); } // Recalculate Cell recalculation; foreach (String depend in dependents) { if (spreadsheetCells.TryGetValue(depend, out recalculation)) { // Set value of cell try { String equalPattern = @"^="; String cellContents = recalculation.cellContents.ToString(); if (Regex.IsMatch(cellContents, equalPattern)) { String parsedContents = cellContents.Substring(1); Formula f = new Formula(parsedContents); recalculation.cellValue = f.Evaluate((x) => (double)GetCellValue(x)); //SetContentsOfCell(recalculation.cellName, recalculation.cellContents.ToString()); } } catch (InvalidCastException) { recalculation.cellValue = new FormulaError(); } catch (FormulaEvaluationException) { recalculation.cellValue = new FormulaError(); } } } // Change changed to true since we made a change to the spreadsheet Changed = true; // Return return(returnValues); }
/// <summary> /// If the formula parameter is null, throws an ArgumentNullException. /// /// Otherwise, if name is null or invalid, throws an InvalidNameException. /// /// Otherwise, if changing the contents of the named cell to be the formula would cause a /// circular dependency, throws a CircularException. (No change is made to the spreadsheet.) /// /// Otherwise, the contents of the named cell becomes formula. The method returns a /// Set consisting of name plus the names of all other cells whose value depends, /// directly or indirectly, on the named cell. /// /// For example, if name is A1, B1 contains A1*2, and C1 contains B1+A1, the /// set {A1, B1, C1} is returned. /// </summary> public abstract ISet <String> SetCellContents(String name, Formula formula);