public override TydNode DeepClone() { TydList c = new TydList(name, Parent, docLine); CopyDataFrom(c); 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)); } }
///<summary> /// Convert a single XML tree into a Tyd tree. /// If expectName is false, it'll be parsed as a list item. ///</summary> public static TydNode TydNodeFromXmlNode(XmlNode xmlRoot) { if (xmlRoot is XmlComment) { return(null); } var newTydName = xmlRoot.Name != "li" ? xmlRoot.Name : null; //Record _attributes here so we can use them later var attributes = new Dictionary <string, string>(); var xmlAttributes = xmlRoot.Attributes; if (xmlAttributes != null) { foreach (XmlAttribute a in xmlAttributes) { attributes[a.Name] = a.Value; } } if (xmlRoot.ChildNodes.Count == 1 && xmlRoot.FirstChild is XmlText) { //It's a string return(new TydString(newTydName, xmlRoot.FirstChild.InnerText)); } else if (xmlRoot.HasChildNodes && xmlRoot.FirstChild.Name == "li") { //Children are named 'li' //It's a list var tydRoot = new TydList(newTydName); tydRoot.SetupAttributes(attributes); foreach (XmlNode xmlChild in xmlRoot.ChildNodes) { tydRoot.AddChild(TydNodeFromXmlNode(xmlChild)); } return(tydRoot); } else { //This case catches _nodes with no children. //Note that the case of no children is ambiguous between list and table; we choose list arbitrarily. //It's a table var tydRoot = new TydTable(newTydName); foreach (XmlNode xmlChild in xmlRoot.ChildNodes) { tydRoot.AddChild(TydNodeFromXmlNode(xmlChild)); } return(tydRoot); } }
/* * 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> /// 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 either strings or nulls: We just keep the existing heir's value if (source is TydString) { return; } //Heir has noinherit attribute: Skip this inheritance { TydCollection heirCol = heir as TydCollection; if (heirCol != null && heirCol.AttributeNoInherit) { return; } } //They're tables: Combine all children of source and heir. Unique-name source nodes are prepended { TydTable sourceObj = source as TydTable; if (sourceObj != null) { TydTable heirTable = (TydTable)heir; for (int i = 0; i < sourceObj.Count; i++) { var sourceChild = sourceObj[i]; var heirMatchingChild = heirTable[sourceChild.Name]; if (heirMatchingChild != null) { ApplyInheritance(sourceChild, heirMatchingChild); } else { heirTable.InsertChild(sourceChild, 0); //Does this need to be DeepClone? } } return; } } //They're lists: Prepend source's children before heir's children { TydList sourceList = source as TydList; if (sourceList != null) { TydList heirList = (TydList)heir; for (int i = 0; i < sourceList.Count; i++) { //Insert at i so the nodes stay in the same order from source to heir heirList.InsertChild(sourceList[i], 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)); } }
///<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); } } } }
///<summary> /// Convert a single XML tree into a Tyd tree. /// If expectName is false, it'll be parsed as a list item. ///</summary> public static TydNode TydNodeFromXmlNode(XmlNode xmlRoot, TydNode tydParent) { if (xmlRoot is XmlComment) { return(null); } string newTydName = xmlRoot.Name != "li" ? xmlRoot.Name : null; //Record attributes here so we can use them later string attClass = null; string attHandle = null; string attSource = null; bool attAbstract = false; var xmlAttributes = xmlRoot.Attributes; if (xmlAttributes != null) { foreach (XmlAttribute a in xmlAttributes) { if (a.Name == "Class") { attClass = a.Value; } else if (a.Name == "Name") { attHandle = a.Value; } else if (a.Name == "ParentName") { attSource = a.Value; } else if (a.Name == "Abstract" && a.Value == "True") { attAbstract = true; } } } if (xmlRoot.ChildNodes.Count == 1 && xmlRoot.FirstChild is XmlText) { //It's a string return(new TydString(newTydName, xmlRoot.FirstChild.InnerText, tydParent)); } else if (xmlRoot.HasChildNodes && xmlRoot.FirstChild.Name == "li") { //Children are named 'li' //It's a list TydList tydRoot = new TydList(newTydName, tydParent); tydRoot.SetupAttributes(attClass, attHandle, attSource, attAbstract); foreach (XmlNode xmlChild in xmlRoot.ChildNodes) { tydRoot.AddChild(TydNodeFromXmlNode(xmlChild, tydRoot)); } return(tydRoot); } else { //This case catches nodes with no children. //Note that the case of no children is ambiguous between list and table; we choose list arbitrarily. //It's a table TydTable tydRoot = new TydTable(newTydName, tydParent); tydRoot.SetupAttributes(attClass, attHandle, attSource, attAbstract); foreach (XmlNode xmlChild in xmlRoot.ChildNodes) { tydRoot.AddChild(TydNodeFromXmlNode(xmlChild, tydRoot)); } return(tydRoot); } }