///<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> /// Read an XML document and convert it to a sequence of Tyd nodes. /// This ignores the XML root and treats each of the root's children as a separate Tyd table. ///</summary> public static IEnumerable <TydNode> TydNodesFromXmlDocument(XmlDocument xmlDocument) { foreach (XmlNode xmlChild in xmlDocument.DocumentElement.ChildNodes) { TydNode newNode = TydNodeFromXmlNode(xmlChild, null); if (newNode != null) { yield return(newNode); } } }
/* * 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> /// 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); } }
/* * 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, bool whitesmiths, int indent = 0, int longestName = 0, bool forceQuotes = false, bool noInlineTables = false) { var braceIndent = whitesmiths ? indent + 1 : indent; //It's a string var str = node as TydString; if (str != null) { if (str.Name != null) { return(IndentString(indent) + node.Name + RepeatString(" ", (Math.Max(0, longestName - node.Name.Length) + 1)) + StringContentWriteable(str.Value, forceQuotes)); } else { return(IndentString(indent) + StringContentWriteable(str.Value, forceQuotes)); } } var doc = node as TydDocument; if (doc != null) { var nameLength = doc.Nodes.Max(x => x.Name.Length); var sb = new StringBuilder(); foreach (var subNode in doc) { sb.AppendLine(Write(subNode, whitesmiths, indent, nameLength, forceQuotes, noInlineTables)); if (subNode is TydCollection) { sb.AppendLine(); } } return(sb.ToString()); } //It's a table var tab = node as TydTable; if (tab != null) { var sb = new StringBuilder(); var simple = !noInlineTables && tab.Parent != null && !(tab.Parent is TydDocument) && IsSimpleCollection(tab); var intro = AppendNodeIntro(tab, sb, indent); //Intro line if (intro && !simple) { sb.AppendLine(); } if (simple) { if (!intro) { sb.Append(IndentString(indent) + Constants.TableStartChar); } else { sb.Append(RepeatString(" ", Math.Max(0, longestName - tab.Name.Length) + 1) + Constants.TableStartChar); } for (var i = 0; i < tab.Count; i++) { sb.Append(i == 0 ? " " : "; "); sb.Append(Write(tab[i], whitesmiths, 0, 0, forceQuotes, noInlineTables)); } sb.Append(" " + Constants.TableEndChar); } else { var nameLength = tab.Nodes.Max(x => x.Name.Length); //Sub-_nodes sb.AppendLine(IndentString(braceIndent) + Constants.TableStartChar); for (var i = 0; i < tab.Count; i++) { sb.AppendLine(Write(tab[i], whitesmiths, indent + 1, nameLength, forceQuotes, noInlineTables)); } sb.Append(IndentString(braceIndent) + Constants.TableEndChar); } return(sb.ToString()); } //It's a list var list = node as TydList; if (list != null) { var sb = new StringBuilder(); var simple = IsSimpleCollection(list); var intro = AppendNodeIntro(list, sb, indent); //Intro line if (intro && !simple) { sb.AppendLine(); } if (simple) { if (!intro) { sb.Append(IndentString(indent) + Constants.ListStartChar); } else { sb.Append(RepeatString(" ", Math.Max(0, longestName - list.Name.Length) + 1) + Constants.ListStartChar); } for (var i = 0; i < list.Count; i++) { sb.Append(i == 0 ? " " : "; "); sb.Append(Write(list[i], whitesmiths, 0, 0, forceQuotes, noInlineTables)); } sb.Append(" " + Constants.ListEndChar); } else { //Sub-_nodes sb.AppendLine(IndentString(braceIndent) + Constants.ListStartChar); for (var i = 0; i < list.Count; i++) { sb.AppendLine(Write(list[i], whitesmiths, indent + 1, 0, forceQuotes, noInlineTables)); } sb.Append(IndentString(braceIndent) + Constants.ListEndChar); } return(sb.ToString()); } throw new ArgumentException(); }
public TydList(string name, TydNode parent, int docLine = -1) : base(name, parent, docLine) { }