User:Maven/entitybot

From Valve Developer Community
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() );
        }
    }
}