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, copy an entity entry from the FGD into the top box, click the convert button, and paste the resulting bottom-box text into the appropriate VDC edit page. You will need to fill in some of the Availability section.
(Feel free to change anything in this article, including the code.)
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace FGD2Wiki
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void fgd2wikiButton_Click( object sender, EventArgs e )
{
this.ConvertFGD2Wiki();
}
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 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();
}
private void ConvertFGD2Wiki()
{
// Get the (trimmed) input string
string input = this.fgdTextbox.Text.Trim();
string token;
//------------------
// Parse the header
//------------------
// Read the entity type: point-based or brush-based
EntityType eEntityType = EntityType.Error;
token = StripTextUntil( ref input, ' ' );
switch ( token )
{
case "@PointClass":
eEntityType = EntityType.PointBased;
break;
case "@SolidClass":
eEntityType = EntityType.BrushBased;
break;
case "@BaseClass":
this.wikiTextbox.Text = "@BaseClass not supported";
return;
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 )
{
// Create a new spawnflag
EntitySpawnflag fl = new EntitySpawnflag();
// Read its data
fl.value = int.Parse( StripTextUntil( ref sFlags, ':' ) );
StripTextUntil( ref sFlags, '"' );
fl.desc = StripTextUntil( ref sFlags, '"' );
StripTextUntil( ref sFlags, ':' );
fl.def = int.Parse( StripTextUntil( ref sFlags, '\n' ) );
// Add it to the list of spawnflags
llSpawnflags.Add( fl );
}
}
// Not spawnflag
else
{
// 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:
this.wikiTextbox.Text = "Unexpected entity type: " + eEntityType;
return;
}
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
this.wikiTextbox.Text = sb.ToString();
}
// TODO: IsEmptyKeyvalues
private bool IsEmptyInput( string s )
{
// TODO: efficiency
switch ( s.ToLower() )
{
case "angles":
case "renderfxchoices":
return true;
default:
return false;
}
}
private bool IsEmptyOutput( string s )
{
// TODO: efficiency
switch ( s.ToLower() )
{
case "angles":
case "parentname":
case "renderfxchoices":
return true;
default:
return false;
}
}
private bool IsEmptyFlag( string s )
{
// TODO: efficiency
switch ( s.ToLower() )
{
case "angles":
case "parentname":
case "targetname":
case "renderfxchoices":
case "basevehicle":
return true;
default:
return false;
}
}
}
}
Form1.Designer.cs
namespace FGD2Wiki
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose( bool disposing )
{
if ( disposing && (components != null) )
{
components.Dispose();
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.fgdTextbox = new System.Windows.Forms.TextBox();
this.wikiTextbox = new System.Windows.Forms.TextBox();
this.panel1 = new System.Windows.Forms.Panel();
this.fgd2wikiButton = new System.Windows.Forms.Button();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
// splitContainer1
//
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.Location = new System.Drawing.Point( 0, 0 );
this.splitContainer1.Name = "splitContainer1";
this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add( this.fgdTextbox );
//
// splitContainer1.Panel2
//
this.splitContainer1.Panel2.Controls.Add( this.wikiTextbox );
this.splitContainer1.Panel2.Controls.Add( this.panel1 );
this.splitContainer1.Size = new System.Drawing.Size( 522, 449 );
this.splitContainer1.SplitterDistance = 205;
this.splitContainer1.TabIndex = 0;
this.splitContainer1.Text = "splitContainer1";
//
// fgdTextbox
//
this.fgdTextbox.Dock = System.Windows.Forms.DockStyle.Fill;
this.fgdTextbox.Font = new System.Drawing.Font( "Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (0)) );
this.fgdTextbox.Location = new System.Drawing.Point( 0, 0 );
this.fgdTextbox.Multiline = true;
this.fgdTextbox.Name = "fgdTextbox";
this.fgdTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.fgdTextbox.Size = new System.Drawing.Size( 522, 205 );
this.fgdTextbox.TabIndex = 0;
//
// wikiTextbox
//
this.wikiTextbox.Dock = System.Windows.Forms.DockStyle.Fill;
this.wikiTextbox.Font = new System.Drawing.Font( "Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (0)) );
this.wikiTextbox.Location = new System.Drawing.Point( 0, 25 );
this.wikiTextbox.Multiline = true;
this.wikiTextbox.Name = "wikiTextbox";
this.wikiTextbox.ReadOnly = true;
this.wikiTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.wikiTextbox.Size = new System.Drawing.Size( 522, 215 );
this.wikiTextbox.TabIndex = 1;
//
// panel1
//
this.panel1.Controls.Add( this.fgd2wikiButton );
this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
this.panel1.Location = new System.Drawing.Point( 0, 0 );
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size( 522, 25 );
this.panel1.TabIndex = 0;
//
// fgd2wikiButton
//
this.fgd2wikiButton.Dock = System.Windows.Forms.DockStyle.Left;
this.fgd2wikiButton.Location = new System.Drawing.Point( 0, 0 );
this.fgd2wikiButton.Name = "fgd2wikiButton";
this.fgd2wikiButton.Size = new System.Drawing.Size( 101, 25 );
this.fgd2wikiButton.TabIndex = 0;
this.fgd2wikiButton.Text = "&Convert";
this.fgd2wikiButton.Click += new System.EventHandler( this.fgd2wikiButton_Click );
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size( 522, 449 );
this.Controls.Add( this.splitContainer1 );
this.Name = "Form1";
this.Text = "FGD to Wiki";
this.splitContainer1.Panel1.ResumeLayout( false );
this.splitContainer1.Panel1.PerformLayout();
this.splitContainer1.Panel2.ResumeLayout( false );
this.splitContainer1.Panel2.PerformLayout();
this.splitContainer1.ResumeLayout( false );
this.panel1.ResumeLayout( false );
this.ResumeLayout( false );
}
#endregion
private System.Windows.Forms.SplitContainer splitContainer1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button fgd2wikiButton;
private System.Windows.Forms.TextBox fgdTextbox;
private System.Windows.Forms.TextBox wikiTextbox;
}
}
Program.cs
If it's not auto-generated for you, you'll need this too.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace FGD2Wiki
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run( new Form1() );
}
}
}