public override TydNode DeepClone() { var c = new TydString(_name, Value, DocLine); c.DocIndexEnd = DocIndexEnd; return(c); }
///<summary> /// Copies all child nodes from source into heir, recursively. /// -If a node appears only in source or only in heir, it is included. /// -If a list appears in both source and heir, source's entries are appended to heir's entries. /// -If a non-list node appears in both source and heir, heir's node is overwritten. ///</summary> private static void ApplyInheritance(TydNode source, TydNode heir) { try { //They're strings: Copy source over heir { TydString sourceStr = source as TydString; if (sourceStr != null) { TydString heirStr = heir as TydString; heirStr.Value = sourceStr.Value; return; } } //They're tables: Combine all children of source and heir, with source having priority in case of duplicates { TydTable sourceObj = source as TydTable; if (sourceObj != null) { TydTable heirObj = (TydTable)heir; for (int i = 0; i < sourceObj.Count; i++) { var sourceChild = sourceObj[i]; var heirMatchingChild = heirObj[sourceChild.Name]; if (heirMatchingChild != null) { ApplyInheritance(sourceChild, heirMatchingChild); } else { heirObj.AddChild(sourceChild); //Does this need to be DeepClone? } } return; } } //They're lists: Append source's entries to heir's entries { TydList sourceList = source as TydList; if (sourceList != null) { TydList heirList = (TydList)heir; for (int i = 0; i < sourceList.Count; i++) { heirList.AddChild(sourceList[i]); //Does this need to be DeepClone? } return; } } } catch (Exception e) { throw new Exception("ApplyInheritance exception: " + e + ".\nsource: (" + source + ")\n" + TydToText.Write(source) + "\ntarget: (" + heir + ")\n" + TydToText.Write(heir)); } }
/* * Possible future features: * - Ability to align string values into a single column * - Ability to write lists/tables with 0 or 1 children on a single line * - Some way to better control which strings get quotes and which don't */ ///<summary> /// Writes a given TydNode, along with all its descendants, as a string, at a given indent level. /// This method is recursive. ///</summary> public static string Write(TydNode node, int indent = 0) { //It's a string TydString str = node as TydString; if (str != null) { return(IndentString(indent) + node.Name + " " + StringContentWriteable(str.Value)); } //It's a table TydTable tab = node as TydTable; if (tab != null) { StringBuilder sb = new StringBuilder(); //Intro line if (AppendNodeIntro(tab, sb, indent) && tab.Count > 0) { sb.AppendLine(); } if (tab.Count == 0) { sb.Append(Constants.TableStartChar.ToString() + Constants.TableEndChar.ToString()); } else { //Sub-nodes sb.AppendLine(IndentString(indent) + Constants.TableStartChar); for (int i = 0; i < tab.Count; i++) { sb.AppendLine(Write(tab[i], indent + 1)); } sb.Append(IndentString(indent) + Constants.TableEndChar); } return(sb.ToString()); } //It's a list TydList list = node as TydList; if (list != null) { StringBuilder sb = new StringBuilder(); //Intro line if (AppendNodeIntro(list, sb, indent) && list.Count > 0) { sb.AppendLine(); } if (list.Count == 0) { sb.Append(Constants.ListStartChar.ToString() + Constants.ListEndChar.ToString()); } else { //Sub-nodes sb.AppendLine(IndentString(indent) + Constants.ListStartChar); for (int i = 0; i < list.Count; i++) { sb.AppendLine(Write(list[i], indent + 1)); } sb.Append(IndentString(indent) + Constants.ListEndChar); } return(sb.ToString()); } throw new ArgumentException(); }
///<summary> /// Recursively parses the string 'doc' starting at char index 'startIndex' and ending when there is an unmatched closing bracket or EOF. /// doc should have any opening bracket already stripped off. /// This recursive method is used both for parsing files, as well as for parsing specific entries inside files. ///</summary> private static IEnumerable <TydNode> Parse(string doc, int startIndex, TydNode parent, bool expectNames = true) { int p = startIndex; //Main loop while (true) { string recordName = null; string recordAttClass = null; string recordAttHandle = null; string recordAttSource = null; bool recordAttAbstract = false; try { //Skip insubstantial chars p = NextSubstanceIndex(doc, p); //We reached EOF, so we're finished if (p == doc.Length) { yield break; } //We reached a closing bracket, so we're finished with this record if (doc[p] == Constants.TableEndChar || doc[p] == Constants.ListEndChar) { yield break; } //Read the record name if we're not reading anonymous records if (expectNames) { recordName = ReadSymbol(doc, ref p); } //Skip whitespace p = NextSubstanceIndex(doc, p); //Read attributes while (doc[p] == Constants.AttributeStartChar) { //Skip past the '*' character p++; //Read the att name string attName = ReadSymbol(doc, ref p); if (attName == Constants.AbstractAttributeName) { //Just reading the abstract name indicates it's abstract, no value is needed recordAttAbstract = true; } else { p = NextSubstanceIndex(doc, p); //Read the att value string attVal = ReadSymbol(doc, ref p); switch (attName) { case Constants.ClassAttributeName: recordAttClass = attVal; break; case Constants.HandleAttributeName: recordAttHandle = attVal; break; case Constants.SourceAttributeName: recordAttSource = attVal; break; default: throw new Exception("Unknown attribute name '" + attName + "' at " + IndexToLocationString(doc, p)); } } p = NextSubstanceIndex(doc, p); } } catch (Exception e) { throw new Exception("Exception parsing Tyd headers at " + IndexToLocationString(doc, p) + ": " + e.ToString(), e); } //Read the record value. //After this is complete, p should be pointing at the char after the last char of the record. if (doc[p] == Constants.TableStartChar) { //It's a table TydTable newTable = new TydTable(recordName, parent, IndexToLine(doc, p)); //Skip past the opening bracket p++; p = NextSubstanceIndex(doc, p); //Recursively parse all of new child's children and add them to it try { foreach (var subNode in Parse(doc, p, newTable, expectNames: true)) { if (usedNames.Contains(subNode.Name)) { throw new FormatException("Duplicate record name " + subNode.Name + " at " + IndexToLocationString(doc, p)); } usedNames.Add(subNode.Name); newTable.AddChild(subNode); p = subNode.docIndexEnd + 1; } } finally { usedNames.Clear(); } p = NextSubstanceIndex(doc, p); if (doc[p] != Constants.TableEndChar) { throw new FormatException("Expected ']' at " + IndexToLocationString(doc, p)); } newTable.docIndexEnd = p; newTable.SetupAttributes(recordAttClass, recordAttHandle, recordAttSource, recordAttAbstract); yield return(newTable); //Move pointer one past the closing bracket p++; } else if (doc[p] == Constants.ListStartChar) { //It's a list TydList newList = new TydList(recordName, parent, IndexToLine(doc, p)); //Skip past the opening bracket p++; p = NextSubstanceIndex(doc, p); //Recursively parse all of new child's children and add them to it foreach (var subNode in Parse(doc, p, newList, expectNames: false)) { newList.AddChild(subNode); p = subNode.docIndexEnd + 1; } p = NextSubstanceIndex(doc, p); if (doc[p] != Constants.ListEndChar) { throw new FormatException("Expected " + Constants.ListEndChar + " at " + IndexToLocationString(doc, p)); } newList.docIndexEnd = p; newList.SetupAttributes(recordAttClass, recordAttHandle, recordAttSource, recordAttAbstract); yield return(newList); //Move pointer one past the closing bracket p++; } else { //It's a string int pStart = p; string val; ParseStringValue(doc, ref p, out val); var strNode = new TydString(recordName, val, parent, IndexToLine(doc, pStart)); strNode.docIndexEnd = p - 1; yield return(strNode); } } }
///<summary> /// Recursively parses the string 'doc' starting at char index 'startIndex' and ending when there is an unmatched closing bracket or EOF. /// doc should have any opening bracket already stripped off. /// This recursive method is used both for parsing files, as well as for parsing specific entries inside files. ///</summary> private static List <TydNode> Parse(string doc, int startIndex, ref int currentLine, bool expectNames = true, List <TydNode> res = null, bool breakOnFirst = false) { var p = startIndex; res = res ?? new List <TydNode>(); //Main loop while (true) { string recordName = null; Dictionary <string, string> attributes = null; try { var before = currentLine; //Skip insubstantial chars p = NextSubstanceIndex(doc, p, ref currentLine); //We reached EOF, so we're finished if (p == doc.Length) { return(res); } //Unhandled semicolon, likely after list or table, ignore it according to spec if (doc[p] == ';') { p++; continue; } //We reached a closing bracket, so we're finished with this record if (doc[p] == Constants.TableEndChar || doc[p] == Constants.ListEndChar) { //To avoid counting lines twice from parent call, we subtract last new lines counted currentLine = before; return(res); } //Read the record _name if we're not reading anonymous records if (expectNames) { recordName = ReadSymbol(doc, currentLine, ref p); } //Skip whitespace p = NextSubstanceIndex(doc, p, ref currentLine); //Read _attributes while (doc[p] == Constants.AttributeStartChar) { //Skip past the '*' character p++; //Read the att _name var attName = ReadSymbol(doc, currentLine, ref p); p = NextSubstanceIndex(doc, p, ref currentLine); if (doc[p] == Constants.AttributeStartChar || doc[p] == Constants.CommentChar || doc[p] == Constants.TableStartChar) { //No attribute value defined SetAttributeValue(ref attributes, attName, null); } else { //Read the att value string value; ParseStringValue(doc, ref p, ref currentLine, out value); SetAttributeValue(ref attributes, attName, value); p = NextSubstanceIndex(doc, p, ref currentLine); } } } catch (Exception e) { throw new Exception("Exception parsing Tyd headers at " + IndexToLocationString(doc, currentLine, p) + ": " + e.ToString(), e); } //Read the record value. //After this is complete, p should be pointing at the char after the last char of the record. if (doc[p] == Constants.TableStartChar) { //It's a table var newTable = new TydTable(recordName, currentLine); //Skip past the opening bracket p++; p = NextSubstanceIndex(doc, p, ref currentLine); //Recursively parse all of new child's children and add them to it foreach (var subNode in Parse(doc, p, ref currentLine, true, newTable.Nodes)) { subNode.Parent = newTable; p = subNode.DocIndexEnd + 1; } p = NextSubstanceIndex(doc, p, ref currentLine); if (doc[p] != Constants.TableEndChar) { throw new FormatException("Expected " + Constants.TableEndChar + " at " + IndexToLocationString(doc, currentLine, p)); } newTable.DocIndexEnd = p; newTable.SetupAttributes(attributes); res.Add(newTable); //Move pointer one past the closing bracket p++; } else if (doc[p] == Constants.ListStartChar) { //It's a list var pStart = p; var newList = new TydList(recordName, currentLine); //Skip past the opening bracket p++; p = NextSubstanceIndex(doc, p, ref currentLine); //Recursively parse all of new child's children and add them to it foreach (var subNode in Parse(doc, p, ref currentLine, false, newList.Nodes)) { subNode.Parent = newList; p = subNode.DocIndexEnd + 1; } p = NextSubstanceIndex(doc, p, ref currentLine); if (p >= doc.Length || doc[p] != Constants.ListEndChar) { throw new FormatException(string.Format("Expected {0} from {1} at {2}", Constants.ListEndChar, IndexToLocationString(doc, newList.DocLine, pStart), IndexToLocationString(doc, currentLine, p))); } newList.DocIndexEnd = p; newList.SetupAttributes(attributes); res.Add(newList); if (breakOnFirst) { return(res); } //Move pointer one past the closing bracket p++; } else { //It's a string var pStart = p; string val; ParseStringValue(doc, ref p, ref currentLine, out val); var strNode = new TydString(recordName, val, currentLine); strNode.DocIndexEnd = p - 1; res.Add(strNode); if (breakOnFirst) { return(res); } } } }