Difference between revisions of "KeyValues (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
m (Full Traversal)
m (kv.GoBack(kv) -> kv.GoBack())
 
(39 intermediate revisions by 12 users not shown)
Line 2: Line 2:
  
 
All KeyValues specific functions in this document are from <tt>public/include/keyvalues.inc</tt>.
 
All KeyValues specific functions in this document are from <tt>public/include/keyvalues.inc</tt>.
 +
 +
''Note: While the following examples are correct code-wise, over the years they have occasionally led people to use KeyValues in cases where they really should be using a [https://wiki.alliedmods.net/SQL_%28SourceMod_Scripting%29 database] instead.''
  
 
=Introduction=
 
=Introduction=
Line 7: Line 9:
 
<pre>"section"
 
<pre>"section"
 
{
 
{
"key" "value"
+
  "key" "value"
 
}</pre>
 
}</pre>
  
 
The <tt>"section"</tt> string denotes the section's name.  The <tt>"key"</tt> string is the key name, and the <tt>"value"</tt>" string is the value.
 
The <tt>"section"</tt> string denotes the section's name.  The <tt>"key"</tt> string is the key name, and the <tt>"value"</tt>" string is the value.
  
KeyValues structures are created with <tt>CreateKeyValues()</tt>.  The Handle must be freed when finished in order to avoid a memory leak.
+
KeyValues structures are created with <tt>new KeyValues()</tt>.  The Handle must be freed when finished in order to avoid a memory leak.
  
 
=Files=
 
=Files=
Line 18: Line 20:
 
<pre>"MyFile"
 
<pre>"MyFile"
 
{
 
{
"STEAM_0:0:7"
+
  "STEAM_0:0:7"
{
+
  {
"name" "crab"
+
    "name"   "crab"
}
+
  }
 
}</pre>
 
}</pre>
  
 
In this example, <tt>STEAM_0:0:7</tt> is a section under <tt>MyFile</tt>, the root node.   
 
In this example, <tt>STEAM_0:0:7</tt> is a section under <tt>MyFile</tt>, the root node.   
  
To load KeyValues from a file, use <tt>FileToKeyValues</tt>  To save KeyValues to a file, use <tt>KeyValuesFromFile</tt>.  For both cases, you must already have a Handle to a KeyValues structure.
+
To load KeyValues from a file, use <tt>kv.ImportFromFile</tt>  To save KeyValues to a file, use <tt>kv.ExportToFile</tt>, where <tt>kv</tt> is a Handle to a KeyValues structure.
  
 
=Traversal=
 
=Traversal=
Line 35: Line 37:
 
Since a KeyValues structure is inherently recursive (it's a tree), the ''traversal stack'' is used to save a history of each ''traversal'' made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section).  The ''top'' of this stack is where all operations take place.   
 
Since a KeyValues structure is inherently recursive (it's a tree), the ''traversal stack'' is used to save a history of each ''traversal'' made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section).  The ''top'' of this stack is where all operations take place.   
  
For example, <tt>KvJumpToKey</tt> will attempt to find a sub-key under the current section.  If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack.  Now, operations such as <tt>KvGetString</tt> will use this new position.
+
For example, <tt>kv.JumpToKey</tt> will attempt to find a sub-key under the current section.  If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack.  Now, operations such as <tt>kv.GetString</tt> will use this new position.
  
There are natives which change the position but do not change the traversal stack.  For example, <tt>KvGotoNextKey</tt> will change the current section being viewed, but there are no push/pop operations to the traversal stack.  This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.
+
There are natives which change the position but do not change the traversal stack.  For example, <tt>kv.GotoNextKey</tt> will change the current section being viewed, but there are no push/pop operations to the traversal stack.  This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.
  
 
More traversal natives:
 
More traversal natives:
*<tt>KvGotoFirstSubKey</tt> - Finds the first sub-section under the current section.  This pushes the section onto the traversal stack.
+
*<tt>kv.GotoFirstSubKey</tt> - Finds the first sub-section under the current section.  This pushes the section onto the traversal stack.
*<tt>KvGoBack</tt> - Pops the traversal stack (moves up one level).
+
*<tt>kv.GoBack</tt> - Pops the traversal stack (moves up one level).
*<tt>KvRewind</tt> - Clears the traversal stack so the current position is the root node.
+
*<tt>kv.Rewind</tt> - Clears the traversal stack so the current position is the root node.
  
 
==Basic Lookup==
 
==Basic Lookup==
 
Let's take our <tt>MyFile</tt> example from above.  How could we retrieve the name "crab" given the Steam ID?
 
Let's take our <tt>MyFile</tt> example from above.  How could we retrieve the name "crab" given the Steam ID?
  
<pawn>bool:GetNameFromSteamID(const String:steamid[], String:name[], maxlength)
+
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)
 
{
 
{
new Handle:kv = CreateKeyValues("MyFile")
+
    KeyValues kv = new KeyValues("MyFile");
FileToKeyValues(kv, "myfile.txt")
+
    kv.ImportFromFile("myfile.txt");
if (!KvJumpToKey(kv, steamid))
 
{
 
return false
 
}
 
KvGetString(kv, "name", name, maxlength)
 
CloseHandle(kv)
 
return true
 
}</pawn>
 
  
'''Note:''' <tt>KvJumpToKey</tt> is a traversal native that changes the nesting level of the traversal stack.  However, <tt>CloseHandle</tt> will not accidentally close only the current level of the KeyValues structure.  It will close the entire thing.
+
    if (!kv.JumpToKey(steamid))
 +
    {
 +
        delete kv;
 +
        return false;
 +
    }
 +
 
 +
    kv.GetString("name", name, maxlength);
 +
    delete kv;
 +
 
 +
    return true;
 +
}</sourcepawn>
 +
 
 +
'''Note:''' <tt>kv.JumpToKey</tt> is a traversal native that changes the nesting level of the traversal stack.  However, <tt>delete</tt> will not accidentally close only the current level of the KeyValues structure.  It will close the entire thing.
  
 
==Iterative Lookup==
 
==Iterative Lookup==
 
Let's modify our previous example to use iteration.  This has the same functionality, but demonstrates how to browse over many sections.
 
Let's modify our previous example to use iteration.  This has the same functionality, but demonstrates how to browse over many sections.
  
<pawn>bool:GetNameFromSteamID(const String:steamid[], String:name[], maxlength)
+
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)
 
{
 
{
new Handle:kv = CreateKeyValues("MyFile")
+
    KeyValues kv = new KeyValues("MyFile");
FileToKeyValues(kv, "myfile.txt")
+
    kv.ImportFromFile("myfile.txt");
  
if (!KvGotoFirstSubKey(kv))
+
    // Jump into the first subsection
{
+
    if (!kv.GotoFirstSubKey())
return false
+
    {
}
+
        delete kv;
 +
        return false;
 +
    }
  
decl String:buffer[255]
+
    // Iterate over subsections at the same nesting level
do
+
    char buffer[255];
{
+
    do
KvGetSectionName(kv, buffer, sizeof(buffer))
+
    {
if (StrEqual(buffer, steamid))
+
        kv.GetSectionName(buffer, sizeof(buffer));
{
+
        if (StrEqual(buffer, steamid))
KvGetString(kv, "name", name, maxlength)
+
        {
CloseHandle(kv)
+
            kv.GetString("name", name, maxlength);
return true
+
            delete kv;
}
+
            return true;
} while (KvGotoNextKey(kv))
+
        }
 +
    } while (kv.GotoNextKey());
  
CloseHandle(kv)
+
    delete kv;
return false
+
   
}</pawn>
+
    return false;
 +
}</sourcepawn>
  
'''Note:''' In this example, note that <tt>KvGotoNextKey</tt> is an iterative function, not a traversal one.  Since it does not change the nesting level, we don't need to call <tt>KvGoBack</tt> to return and continue iterating.
+
'''Note:''' In this example, note that <tt>kv.GotoNextKey</tt> is an iterative function, not a traversal one.  Since it does not change the nesting level, we don't need to call <tt>kv.GoBack</tt> to return and continue iterating.
  
 
==Full Traversal==
 
==Full Traversal==
 
Let's say we wanted to browse every section of a KeyValues file.  An example of this might look like:
 
Let's say we wanted to browse every section of a KeyValues file.  An example of this might look like:
<pawn>BrowseKeyValues(Handle:kv)
+
<sourcepawn>void BrowseKeyValues(KeyValues kv)
 
{
 
{
do
+
    do
{
+
    {
if (KvGotoFirstSubKey(kv))
+
        // You can read the section/key name by using kv.GetSectionName here.
{
+
 
BrowseKeyValues(kv)
+
        if (kv.GotoFirstSubKey(false))
KvGoBack(kv)
+
        {
}
+
            // Current key is a section. Browse it recursively.
} while (KvGotoNextKey(kv))
+
            BrowseKeyValues(kv);
}</pawn>
+
            kv.GoBack();
 +
        }
 +
        else
 +
        {
 +
            // Current key is a regular key, or an empty section.
 +
            if (kv.GetDataType(NULL_STRING) != KvData_None)
 +
            {
 +
                // Read value of key here (use NULL_STRING as key name). You can
 +
                // also get the key name by using kv.GetSectionName here.
 +
            }
 +
            else
 +
            {
 +
                // Found an empty sub section. It can be handled here if necessary.
 +
            }
 +
        }
 +
    } while (kv.GotoNextKey(false));
 +
}</sourcepawn>
 +
 
 +
This function will browse an entire KeyValues structure.  Note that every successful call to <tt>kv.GotoFirstSubKey</tt> is paired with a call to <tt>kv.GoBack</tt>.  This is because the former pushes onto the traversal stack.  We must pop the position off so we can continue browsing from our old position.
  
This function will browse an entire KeyValues structure.  Note that every successful call to <tt>KvGotoFirstSubKey</tt> is paired with a call to <tt>KvGoBack</tt>.  This is because the former pushes onto the traversal stackWe must pop the position off so we can continue browsing from our old position.
+
Also note that <tt>keyOnly</tt> is set to false in both <tt>kv.GotoFirstSubKey</tt> and <tt>kv.GotoNextKey</tt> so that it will jump to regular keys and not just between sectionsBecause of this we also need to check if <tt>kv.GotoFirstSubKey</tt> succeeded moving to a regular key before we read the value. This is simply done by getting the data type of the current keyIf <tt>kv.GotoFirstSubKey</tt> failed to move to any key (the section is empty), the cursor is still on the section, which doesn't have a data type.  If there is a data type, we've confirmed that it's a regular key.
  
 
=Deletion=
 
=Deletion=
 
There are two ways to delete sections from a KeyValues structure:
 
There are two ways to delete sections from a KeyValues structure:
*<tt>KvDeleteKey</tt> - Safely removes a named sub-section and any of its child sections/keys.
+
*<tt>kv.DeleteKey</tt> - Safely removes a named sub-section and any of its child sections/keys.
*<tt>KvDeleteThis</tt> - Safely removes the current position if possible.
+
*<tt>kv.DeleteThis</tt> - Safely removes the current position if possible.
  
 
==Simple Deletion==
 
==Simple Deletion==
 
First, let's use our "basic lookup" example to delete a Steam ID section:
 
First, let's use our "basic lookup" example to delete a Steam ID section:
<pawn>RemoveSteamID(const String:steamid[])
+
<sourcepawn>
 +
void RemoveSteamID(const char[] steamid)
 
{
 
{
new Handle:kv = CreateKeyValues("MyFile")
+
    KeyValues kv = new KeyValues("MyFile");
FileToKeyValues(kv, "myfile.txt")
+
    kv.ImportFromFile("myfile.txt");
if (!KvGotoFirstSubKey(kv))
 
{
 
return
 
}
 
KvDeleteKey(kv, steamid)
 
KvRewind(kv)
 
KeyValuesToFile(kv, "myfile.txt")
 
CloseHandle(kv)
 
}</pawn>
 
  
'''Note:''' We called <tt>KvRewind</tt> so the file would be dumped from the root position, instead of the current one.
+
    if (!kv.JumpToKey(steamid))
 +
    {
 +
        delete kv;
 +
        return;
 +
    }
 +
 
 +
    kv.DeleteThis();
 +
    kv.Rewind();
 +
    kv.ExportToFile("myfile.txt");
 +
    delete kv;
 +
}
 +
</sourcepawn>
 +
 
 +
'''Note:''' We called <tt>kv.Rewind</tt> so the file would be dumped from the root position, instead of the current one.
  
 
==Iterative Deletion==
 
==Iterative Deletion==
Likewise, let's show how our earlier iterative example could be adapted for deleting a section.  For this we can use <tt>KvDeleteThis</tt>, which deletes the current position from the previous position in the traversal stack.
+
Likewise, let's show how our earlier iterative example could be adapted for deleting a section.  For this we can use <tt>kv.DeleteThis</tt>, which deletes the current position from the previous position in the traversal stack.
  
<pawn>RemoveSteamID(const String:steamid[])
+
<sourcepawn>void RemoveSteamID(const char[] steamid)
 
{
 
{
new Handle:kv = CreateKeyValues("MyFile")
+
    KeyValues kv = new KeyValues("MyFile");
FileToKeyValues(kv, "myfile.txt")
+
    kv.ImportFromFile("myfile.txt");
  
if (!KvGotoFirstSubKey(kv))
+
    if (!kv.JumpToKey(steamid))
{
+
    {
return
+
        delete kv;
}
+
        return;
 +
    }
  
decl String:buffer[255]
+
    char buffer[255];
do
+
    do
{
+
    {
KvGetSectionName(kv, buffer, sizeof(buffer))
+
        kv.GetSectionName(buffer, sizeof(buffer))
if (StrEqual(buffer, steamid))
+
        if (StrEqual(buffer, steamid))
{
+
        {
KvDeleteThis(kv)
+
            kv.DeleteThis();
CloseHandle(kv)
+
            delete kv;
return
+
            return;
}
+
        }
} while (KvGotoNextKey(kv))
+
    } while (kv.GotoNextKey());
  
CloseHandle(kv)
+
  delete kv;
}</pawn>
+
}</sourcepawn>
  
 
==Full Deletion==
 
==Full Deletion==
Lastly, let's take a look at how we would delete all (or some) keys.  <tt>KvDeleteThis</tt> has the special property that it can act as an automatic iterator.  When it deletes a key, it automatically attempts to advance to the next one, as <tt>KvGotoNextKey</tt> would.  If it can't find any more keys, it simply pops the traversal stack.   
+
Now, let's take a look at how we would delete all (or some) keys.  <tt>kv.DeleteThis</tt> has the special property that it can act as an automatic iterator.  When it deletes a key, it automatically attempts to advance to the next one, as <tt>kv.GotoNextKey</tt> would.  If it can't find any more keys, it simply pops the traversal stack.   
  
 
An example of deleting all SteamIDs that have blank names:
 
An example of deleting all SteamIDs that have blank names:
<pawn>DeleteAll(Handle:kv)
+
<sourcepawn>void DeleteAll(KeyValues kv)
 
{
 
{
if (!KvGotoFirstSubKey(kv))
+
    if (!kv.GotoFirstSubKey())
{
+
    {
return
+
        delete kv;
}
+
        return;
 +
    }
 +
 
 +
    for (;;)
 +
    {
 +
        char name[4];
 +
        kv.GetString("name", name, sizeof(name));
 +
        if (name[0] == '\0')
 +
        {
 +
            if (kv.DeleteThis() < 1)
 +
            {
 +
                break;
 +
            }
 +
        }
 +
        else if (!kv.GotoNextKey())
 +
        {
 +
            break;
 +
        }
 +
    }
 +
 
 +
  delete kv;
 +
}</sourcepawn>
  
for (;;)
+
While at first this loop looks infinite, it is not.  If <tt>kv.DeleteThis</tt> fails to find a subsequent key, it will break out of the loop.  Similarly, if <tt>kv.GotoNextKey</tt> fails, the loop will end.
{
 
decl String:name[4]
 
KvGetString(kv, name, sizeof(name))
 
if (name[0] == '\0')
 
{
 
if (KvDeleteThis(kv) < 1)
 
{
 
break
 
}
 
} else if (!KvGotoNextKey(kv)) {
 
break
 
}
 
}
 
}</pawn>
 
  
While at first this loop looks infinite, it is not.  If <tt>KvDeleteThis</tt> fails to find a subsequent key, it will break out of the loop. Similarly, if <tt>KvGotoNextKey</tt> fails, the loop will end.
+
==KeyValue Creation==
 +
This is how you would create the <tt>MyFile</tt> example from above with the KeyValue API
 +
<sourcepawn>KeyValues kv = new KeyValues("MyFile");
 +
kv.JumpToKey("STEAM_0:0:7", true);
 +
kv.SetString("name", "crab");
 +
kv.Rewind();
 +
kv.ExportToFile("C:\\javalia.txt");
 +
delete kv;</sourcepawn>
  
[[Category:SourceMod Development]]
 
 
[[Category:SourceMod Scripting]]
 
[[Category:SourceMod Scripting]]
 +
{{LanguageSwitch}}

Latest revision as of 07:00, 3 April 2020

KeyValues are simple, tree-based structures used for storing nested sections containing key/value pairs. Detailed information on KeyValues can be seen at the Valve Developer Wiki.

All KeyValues specific functions in this document are from public/include/keyvalues.inc.

Note: While the following examples are correct code-wise, over the years they have occasionally led people to use KeyValues in cases where they really should be using a database instead.

Introduction

KeyValues consist of a nodes, or sections, which contain pairs of keys and values. A section looks like this:

"section"
{
  "key" "value"
}

The "section" string denotes the section's name. The "key" string is the key name, and the "value"" string is the value.

KeyValues structures are created with new KeyValues(). The Handle must be freed when finished in order to avoid a memory leak.

Files

KeyValues can be exported and imported via KeyValues files. These files always consist of a root node and any number of sub-keys or sub-sections. For example, a file might look like this:

"MyFile"
{
  "STEAM_0:0:7"
  {
    "name"    "crab"
  }
}

In this example, STEAM_0:0:7 is a section under MyFile, the root node.

To load KeyValues from a file, use kv.ImportFromFile To save KeyValues to a file, use kv.ExportToFile, where kv is a Handle to a KeyValues structure.

Traversal

SourceMod provides natives for traversing over a KeyValues structure. However, it is important to understand how this traversal works. Internally, SourceMod keeps track of two pieces of information:

  • The root node
  • A traversal stack

Since a KeyValues structure is inherently recursive (it's a tree), the traversal stack is used to save a history of each traversal made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section). The top of this stack is where all operations take place.

For example, kv.JumpToKey will attempt to find a sub-key under the current section. If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack. Now, operations such as kv.GetString will use this new position.

There are natives which change the position but do not change the traversal stack. For example, kv.GotoNextKey will change the current section being viewed, but there are no push/pop operations to the traversal stack. This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.

More traversal natives:

  • kv.GotoFirstSubKey - Finds the first sub-section under the current section. This pushes the section onto the traversal stack.
  • kv.GoBack - Pops the traversal stack (moves up one level).
  • kv.Rewind - Clears the traversal stack so the current position is the root node.

Basic Lookup

Let's take our MyFile example from above. How could we retrieve the name "crab" given the Steam ID?

bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)
{
    KeyValues kv = new KeyValues("MyFile");
    kv.ImportFromFile("myfile.txt");
 
    if (!kv.JumpToKey(steamid))
    {
        delete kv;
        return false;
    }
 
    kv.GetString("name", name, maxlength);
    delete kv;
 
    return true;
}

Note: kv.JumpToKey is a traversal native that changes the nesting level of the traversal stack. However, delete will not accidentally close only the current level of the KeyValues structure. It will close the entire thing.

Iterative Lookup

Let's modify our previous example to use iteration. This has the same functionality, but demonstrates how to browse over many sections.

bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)
{
    KeyValues kv = new KeyValues("MyFile");
    kv.ImportFromFile("myfile.txt");
 
    // Jump into the first subsection
    if (!kv.GotoFirstSubKey())
    {
        delete kv;
        return false;
    }
 
    // Iterate over subsections at the same nesting level
    char buffer[255];
    do
    {
        kv.GetSectionName(buffer, sizeof(buffer));
        if (StrEqual(buffer, steamid))
        {
            kv.GetString("name", name, maxlength);
            delete kv;
            return true;
        }
    } while (kv.GotoNextKey());
 
    delete kv;
 
    return false;
}

Note: In this example, note that kv.GotoNextKey is an iterative function, not a traversal one. Since it does not change the nesting level, we don't need to call kv.GoBack to return and continue iterating.

Full Traversal

Let's say we wanted to browse every section of a KeyValues file. An example of this might look like:

void BrowseKeyValues(KeyValues kv)
{
    do
    {
        // You can read the section/key name by using kv.GetSectionName here.
 
        if (kv.GotoFirstSubKey(false))
        {
            // Current key is a section. Browse it recursively.
            BrowseKeyValues(kv);
            kv.GoBack();
        }
        else
        {
            // Current key is a regular key, or an empty section.
            if (kv.GetDataType(NULL_STRING) != KvData_None)
            {
                // Read value of key here (use NULL_STRING as key name). You can
                // also get the key name by using kv.GetSectionName here.
            }
            else
            {
                // Found an empty sub section. It can be handled here if necessary.
            }
        }
    } while (kv.GotoNextKey(false));
}

This function will browse an entire KeyValues structure. Note that every successful call to kv.GotoFirstSubKey is paired with a call to kv.GoBack. This is because the former pushes onto the traversal stack. We must pop the position off so we can continue browsing from our old position.

Also note that keyOnly is set to false in both kv.GotoFirstSubKey and kv.GotoNextKey so that it will jump to regular keys and not just between sections. Because of this we also need to check if kv.GotoFirstSubKey succeeded moving to a regular key before we read the value. This is simply done by getting the data type of the current key. If kv.GotoFirstSubKey failed to move to any key (the section is empty), the cursor is still on the section, which doesn't have a data type. If there is a data type, we've confirmed that it's a regular key.

Deletion

There are two ways to delete sections from a KeyValues structure:

  • kv.DeleteKey - Safely removes a named sub-section and any of its child sections/keys.
  • kv.DeleteThis - Safely removes the current position if possible.

Simple Deletion

First, let's use our "basic lookup" example to delete a Steam ID section:

void RemoveSteamID(const char[] steamid)
{
    KeyValues kv = new KeyValues("MyFile");
    kv.ImportFromFile("myfile.txt");
 
    if (!kv.JumpToKey(steamid))
    {
        delete kv;
        return;
    }
 
    kv.DeleteThis();
    kv.Rewind();
    kv.ExportToFile("myfile.txt");
    delete kv;
}

Note: We called kv.Rewind so the file would be dumped from the root position, instead of the current one.

Iterative Deletion

Likewise, let's show how our earlier iterative example could be adapted for deleting a section. For this we can use kv.DeleteThis, which deletes the current position from the previous position in the traversal stack.

void RemoveSteamID(const char[] steamid)
{
    KeyValues kv = new KeyValues("MyFile");
    kv.ImportFromFile("myfile.txt");
 
    if (!kv.JumpToKey(steamid))
    {
        delete kv;
        return;
    }
 
    char buffer[255];
    do
    {
        kv.GetSectionName(buffer, sizeof(buffer))
        if (StrEqual(buffer, steamid))
        {
            kv.DeleteThis();
            delete kv;
            return;
        }
    } while (kv.GotoNextKey());
 
  delete kv;
}

Full Deletion

Now, let's take a look at how we would delete all (or some) keys. kv.DeleteThis has the special property that it can act as an automatic iterator. When it deletes a key, it automatically attempts to advance to the next one, as kv.GotoNextKey would. If it can't find any more keys, it simply pops the traversal stack.

An example of deleting all SteamIDs that have blank names:

void DeleteAll(KeyValues kv)
{
    if (!kv.GotoFirstSubKey())
    {
        delete kv;
        return;
    }
 
    for (;;)
    {
        char name[4];
        kv.GetString("name", name, sizeof(name));
        if (name[0] == '\0')
        {
            if (kv.DeleteThis() < 1)
            {
                break;
            }
        }
        else if (!kv.GotoNextKey()) 
        {
            break;
        } 
    }
 
  delete kv;
}

While at first this loop looks infinite, it is not. If kv.DeleteThis fails to find a subsequent key, it will break out of the loop. Similarly, if kv.GotoNextKey fails, the loop will end.

KeyValue Creation

This is how you would create the MyFile example from above with the KeyValue API

KeyValues kv = new KeyValues("MyFile");
kv.JumpToKey("STEAM_0:0:7", true);
kv.SetString("name", "crab");
kv.Rewind();
kv.ExportToFile("C:\\javalia.txt");
delete kv;
Warning: This template (and by extension, language format) should not be used, any pages using it should be switched to Template:Languages

View this page in:  English  Russian  简体中文(Simplified Chinese)