User:Maven/entitybot
Jump to navigation
Jump to search
Here is code for a small app to convert FGD entries into markup for an entity entry in this wiki.
Notes
This code is in the public domain.
It uses C# 2.0, and comes without support. Please excuse its ugly kuldgeness. The correct way would have been to use Flex/Bison.
To use this, pass an entity entry from the FGD as a parameter to Conversion.ConvertFGD2Wiki
and returned string into the appropriate VDC edit page. You will need to fill in some of the Availability section and make sure that there are no missing templates.
This code does not include a UI.
(Feel free to change anything in this article, including the code.)
using System; using System.Collections.Generic; using System.Text; namespace FGD2Wiki { public class Conversion { private enum EntityType { Error = 0, PointBased, BrushBased, }; private class EntityInput { public string name; public string type; public string desc; public string ToWiki() { StringBuilder sb = new StringBuilder(); sb.Append( "*'''" ); sb.Append( this.name ); if ( this.type != "void" ) { sb.Append( " <" ); sb.Append( this.type ); sb.Append( ">" ); } sb.AppendLine( "'''" ); sb.Append( ":" ); sb.AppendLine( this.desc ); return sb.ToString(); } }; private class EntityOutput { public string name; public string type; public string desc; public string ToWiki() { StringBuilder sb = new StringBuilder(); sb.Append( "*'''" ); sb.Append( this.name ); if ( this.type != "void" ) { sb.Append( " <" ); sb.Append( this.type ); sb.Append( ">" ); } sb.AppendLine( "'''" ); sb.Append( ":" ); sb.AppendLine( this.desc ); return sb.ToString(); } }; private struct Pair<A, B> { public Pair( A a, B b ) { this.a = a; this.b = b; } public A a; public B b; } private class EntityKeyvalue { public string name; public string type; public string name2; // the SmartEdit name public string desc; public string def; public List<Pair<int, string>> choices = new List<Pair<int, string>>(); public string ToWiki() { StringBuilder sb = new StringBuilder(); // Add the name sb.Append( "*'''" ); sb.Append( this.name ); sb.AppendLine( "'''" ); // Add the type sb.Append( ":<" ); switch ( this.type ) { case "angle": case "angles": case "vecline": case "byte": case "color255": sb.Append( "[[" ); sb.Append( this.type ); sb.Append( "]]" ); break; case "boolean": case "bool": sb.Append( "[[Wikipedia:boolean|boolean]]" ); break; default: sb.Append( this.type ); break; } sb.Append( "> " ); // Add the SmartEdit name to the desc if the desc is shortish if ( this.desc.Length < 12 ) { sb.Append( this.name2 ); sb.Append( ". " ); } // Add the description sb.Append( this.desc ); // Add a period if there isn't one aleady if ( this.desc.Length > 0 && !this.desc.EndsWith( "." ) ) sb.Append( "." ); /* // Give the default value if ( this.def != "" ) { sb.Append( " Default: " ); sb.Append( this.def ); } */ sb.AppendLine(); // If this is a 'choices' variable, create the choice table if ( this.type == "choices" ) { sb.AppendLine( ":{|" ); sb.AppendLine( "! Literal value || Description" ); foreach ( Pair<int, string> pair in choices ) { sb.AppendLine( "|-" ); sb.Append( "| " ); sb.Append( pair.a ); sb.Append( " || " ); sb.AppendLine( pair.b ); } sb.AppendLine( "|}" ); } return sb.ToString(); } }; private class EntitySpawnflag { public int value; public string desc; public int def; }; private static string StripTextUntil( ref string s, char c ) { int i = s.IndexOf( c ); if ( i < 0 ) { string m = s.Trim(); s = ""; return m; } string n = s.Substring( 0, i ); s = s.Substring( i + 1 ).TrimStart(); return n.Trim(); } public static string ConvertFGD2Wiki( string input ) { string token; // Trim input string input = input.Trim(); //------------------ // Parse the header //------------------ // Read the entity type: point-based or brush-based EntityType eEntityType = EntityType.Error; token = StripTextUntil( ref input, ' ' ); switch ( token ) { case "@PointClass": case "@FilterClass": case "@NPCClass": eEntityType = EntityType.PointBased; break; case "@SolidClass": eEntityType = EntityType.BrushBased; break; case "@BaseClass": return "@BaseClass not supported"; default: eEntityType = EntityType.Error; break; } // Read everything until the equal sign string[] basetypes = new string[0]; string sEntityParams = StripTextUntil( ref input, '=' ); while ( sEntityParams.Length > 0 ) { // Get the param name string sParamName = StripTextUntil( ref sEntityParams, '(' ); string sParamData = StripTextUntil( ref sEntityParams, ')' ); switch ( sParamName ) { case "base": { // Split into individual items basetypes = sParamData.Split( ',' ); // Remove whitespace for ( int j = 0; j < basetypes.Length; ++j ) basetypes[j] = basetypes[j].Trim().ToLower(); // Place targetname and parentname go first, if they exist, followed by the other templates List<string> llBaseTypes = new List<string>( basetypes ); // temporary linked list // TODO: this way is cheesy and inefficient; fix it if ( llBaseTypes.Contains( "parentname" ) ) { // Move parentname to the front llBaseTypes.Remove( "parentname" ); llBaseTypes.Insert( 0, "parentname" ); } if ( llBaseTypes.Contains( "targetname" ) ) { // Move targetname to the front llBaseTypes.Remove( "targetname" ); llBaseTypes.Insert( 0, "targetname" ); } basetypes = llBaseTypes.ToArray(); // convert back to array break; } default: /* ignore it */ break; } } // TODO: not all entities have a description; handle that case // Read the entity name string sEntityName = StripTextUntil( ref input, ':' ); // Read the entity description string sEntityDescriptionUnparsed = StripTextUntil( ref input, '[' ); string sEntityDescription = ""; while ( sEntityDescriptionUnparsed.Length > 0 ) { // Ignore everything until a quote StripTextUntil( ref sEntityDescriptionUnparsed, '"' ); // Take everything until the next quote sEntityDescription = sEntityDescription + StripTextUntil( ref sEntityDescriptionUnparsed, '"' ) + " "; // TODO: this will choke on escaped quotes } //--------------- // Read the body //--------------- string sBody = StripTextUntil( ref input, '@' ); List<EntityInput> llInputs = new List<EntityInput>(); List<EntityOutput> llOutputs = new List<EntityOutput>(); List<EntityKeyvalue> llKeyvalues = new List<EntityKeyvalue>(); List<EntitySpawnflag> llSpawnflags = new List<EntitySpawnflag>(); while ( sBody.Length > 0 && !sBody.StartsWith( "]" ) ) { // Check for line comment while ( sBody.StartsWith( "//" ) ) { // Eat everything until the next newline StripTextUntil( ref sBody, '\n' ); } // Get the item part of the next item:value pair string sItem = StripTextUntil( ref sBody, ')' ); // Input if ( sItem.StartsWith( "input " ) ) { // Create a new input EntityInput ei = new EntityInput(); // Discard the "input" part StripTextUntil( ref sItem, ' ' ); // Read its information ei.name = StripTextUntil( ref sItem, '(' ); ei.type = sItem; // Read the desc StripTextUntil( ref sBody, '"' ); ei.desc = StripTextUntil( ref sBody, '"' ); // Add it to the list of inputs llInputs.Add( ei ); } // Output else if ( sItem.StartsWith( "output " ) ) { // Create a new output EntityOutput eo = new EntityOutput(); // Discard the "output" part StripTextUntil( ref sItem, ' ' ); // Read its information eo.name = StripTextUntil( ref sItem, '(' ); eo.type = StripTextUntil( ref sItem, ')' ); // Read the desc StripTextUntil( ref sBody, '"' ); eo.desc = StripTextUntil( ref sBody, '"' ); // Add it to the list of outputs llOutputs.Add( eo ); } // Keyvalue else { // Read the name string sName = StripTextUntil( ref sItem, '(' ); // Is it the spawnflags field? if ( sName == "spawnflags" ) { StripTextUntil( ref sBody, '[' ); string sFlags = StripTextUntil( ref sBody, ']' ); while ( sFlags.Length > 0 ) { // Check for line comment while ( sFlags.StartsWith( "//" ) ) { // Eat everything until the next newline StripTextUntil( ref sFlags, '\n' ); } // Create a new spawnflag EntitySpawnflag fl = new EntitySpawnflag(); // Read its data string sFlag = StripTextUntil( ref sFlags, '\n' ); fl.value = int.Parse( StripTextUntil( ref sFlag, ':' ) ); StripTextUntil( ref sFlag, '"' ); fl.desc = StripTextUntil( ref sFlag, '"' ); StripTextUntil( ref sFlag, ':' ); fl.def = int.Parse( StripTextUntil( ref sFlag, '/' ) ); // Check for a flag comment if ( sFlag.StartsWith( "/" ) ) fl.desc += " (" + sFlag.Substring(1).Trim() + ")"; // Add it to the list of spawnflags llSpawnflags.Add( fl ); } } // Not spawnflag else { // TODO: handle multi-line strings in desc // Create a new keyvalue EntityKeyvalue kv = new EntityKeyvalue(); // Read its information kv.name = sName; kv.type = StripTextUntil( ref sItem, ')' ); // Read the SmartEdit name StripTextUntil( ref sBody, '"' ); kv.name2 = StripTextUntil( ref sBody, '"' ); StripTextUntil( ref sBody, ':' ); // Is this a 'choices' keyvalue? if ( kv.type == "choices" ) { // Read the default value kv.def = StripTextUntil( ref sBody, '=' ); // Parse the choices StripTextUntil( ref sBody, '[' ); string sChoices = StripTextUntil( ref sBody, ']' ); while ( sChoices.Length > 0 ) { // Create a new int,string pair Pair<int, string> pair = new Pair<int, string>(); // Parse the choice info pair.a = int.Parse( StripTextUntil( ref sChoices, ':' ) ); StripTextUntil( ref sChoices, '"' ); pair.b = StripTextUntil( ref sChoices, '"' ); // Add the pair kv.choices.Add( pair ); } // No desc (the SmartEdit name will be used) kv.desc = ""; } // Not a 'choices' keyvalue else { // Read the rest of the line String sLine = StripTextUntil( ref sBody, '\n' ); // Read the default value if ( sLine.StartsWith( "\"" ) ) { // This is the case where it's in quotes StripTextUntil( ref sLine, '"' ); kv.def = StripTextUntil( ref sLine, '"' ); } else { // This is the case where it isn't (it's an integer or blank) kv.def = StripTextUntil( ref sLine, ':' ); } // Read the desc StripTextUntil( ref sLine, '"' ); kv.desc = StripTextUntil( ref sLine, '"' ); } // Add it to the list of keyvalues llKeyvalues.Add( kv ); } } } //------------------------ // Create the output text //------------------------ StringBuilder sb = new StringBuilder(); // Create the wrongtitle line sb.Append( "{{wrongtitle|title=" ); sb.Append( sEntityName ); sb.AppendLine( "}}" ); sb.AppendLine(); // Create the Entity Description section sb.AppendLine( "==Entity Description==" ); sb.AppendLine( sEntityDescription ); sb.AppendLine(); // Create the Availability section sb.AppendLine( "==Availability==" ); sb.Append( "{{in game|" ); switch ( eEntityType ) { case EntityType.PointBased: sb.Append( "point" ); break; case EntityType.BrushBased: sb.Append( "brush" ); break; default: return "Unexpected entity type: " + eEntityType; } sb.AppendLine( "}} ???" ); sb.AppendLine( "{{in code|class=???|file=???}}" ); sb.AppendLine(); // Create the Keyvalues section sb.AppendLine( "==Keyvalues==" ); foreach ( string template in basetypes ) { sb.Append( "*{{kv " ); sb.Append( template ); sb.AppendLine( "}}" ); } foreach ( EntityKeyvalue kv in llKeyvalues ) { sb.Append( kv.ToWiki() ); } sb.AppendLine(); // Create the Flags section StringBuilder sbFlags = new StringBuilder(); foreach ( string template in basetypes ) { if ( IsEmptyFlag( template ) ) continue; sbFlags.Append( "*{{fl " ); sbFlags.Append( template ); sbFlags.AppendLine( "}}" ); } foreach ( EntitySpawnflag fl in llSpawnflags ) { sbFlags.Append( "*" ); sbFlags.Append( fl.value ); sbFlags.Append( " : " ); sbFlags.AppendLine( fl.desc ); } // If flag data is not empty, add the Flags section if ( sbFlags.ToString().Length > 0 ) { sb.AppendLine( "==Flags==" ); sb.Append( sbFlags.ToString() ); sb.AppendLine(); } // Create the Inputs section StringBuilder sbInputs = new StringBuilder(); foreach ( string template in basetypes ) { if ( IsEmptyInput( template ) ) continue; sbInputs.Append( "*{{i " ); sbInputs.Append( template ); sbInputs.AppendLine( "}}" ); } foreach ( EntityInput ei in llInputs ) { sbInputs.Append( ei.ToWiki() ); } // If input data is not empty, add the Inputs section if ( sbInputs.ToString().Length > 0 ) { sb.AppendLine( "==Inputs==" ); sb.Append( sbInputs.ToString() ); sb.AppendLine(); } // Create the Outputs sections StringBuilder sbOutputs = new StringBuilder(); foreach ( string template in basetypes ) { if ( IsEmptyOutput( template ) ) continue; sbOutputs.Append( "*{{o " ); sbOutputs.Append( template ); sbOutputs.AppendLine( "}}" ); } foreach ( EntityOutput eo in llOutputs ) { sbOutputs.Append( eo.ToWiki() ); } // If output data is not empty, add the Outputs section if ( sbOutputs.ToString().Length > 0 ) { sb.AppendLine( "==Outputs==" ); sb.Append( sbOutputs.ToString() ); sb.AppendLine(); } // Add the Category:Entities line sb.AppendLine( "[[Category:Entities]]" ); sb.AppendLine(); /* // TEMP: dump the tokens to the output box foreach ( string token in tokens ) sb.AppendLine( token ); */ // Output the results return sb.ToString(); } // TODO: check FGD instead of using the hard-coding below private static bool IsEmptyFlag( string s ) { // TODO: efficiency switch ( s.ToLower() ) { case "angles": case "parentname": case "targetname": case "renderfxchoices": case "basevehicle": case "renderfields": case "basefilter": case "origin": case "shadow": case "global": case "enabledisable": case "baseplat": case "button": return true; default: return false; } } private static bool IsEmptyInput( string s ) { // TODO: efficiency switch ( s.ToLower() ) { case "angles": case "renderfxchoices": case "renderfields": case "origin": case "global": return true; default: return false; } } private static bool IsEmptyOutput( string s ) { // TODO: efficiency switch ( s.ToLower() ) { case "angles": case "parentname": case "renderfxchoices": case "renderfields": case "origin": case "shadow": case "global": case "enabledisable": case "baseplat": return true; default: return false; } } } // class Conversion } // namespace FGD2Wiki