///<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); } }