Skip to main content
Deno 2 is finally here 🎉️
Learn more

easy-keyvalues

CI CodeQL npm tag support tag support tag

coverage coverage coverage coverage

The reason for writing a library is that I want to edit KeyValues text, format the text when saving, and keep the comments, support auto load #base on KeyValues, etc.

简体中文

I completely rewrote the library to not be compatible with previous versions, so the version starts from 1.0.0.

Installation

npm i easy-keyvalues
or
yarn add easy-keyvalues

UTF-8 BOM

The BOM will be automatically removed when load this format file.

KeyValues

Features

  • Retain comments
  • Support Node.js and browser
  • Support auto load #base

Import

import {
    KeyValues,
} from 'easy-keyvalues';

KeyValues.Load(file: string): Promise<KeyValues>;
KeyValues.Save(): Promise<void>;
KeyValues.Save(otherFile: string): Promise<void>;

Usages

// Parse KeyValues text
const kv = await KeyValues.Load('/path/to/file.txt');
console.log(kv.toString());

KeyValues’s value and children

Property Type Description
value string It represents the value of KeyValues, which is always of type string and does not convert numeric strings to number during parsing.
children KeyValues[] It represents the children of KeyValues, which means that this kv is an object and the children are its properties.
parent KeyValues It represents the parent node to which the KeyValues belong; the root node has no parent.

Note that value and children are mutually exclusive, one of them exists and the other is undefined, which can be determined by HasChildren()

Related Methods:

GetChildren(): Readonly<KeyValues[]> // When children is undefined return empty array
GetChildCount(): number
GetFirstChild(): KeyValues | undefined
GetLastChild(): KeyValues | undefined
GetValue(): string // When value is undefined return empty string
HasChildren(): boolean
GetParent(): KeyValues | undefined

Root Node

The KeyValues returned after parsing by KeyValues.Parse() is a root node, whose method IsRoot() will return true and is forced to have children.

#base

The purpose of this library is to edit the KV, so after loading #base it does not merge all the KeyValues nodes in #base into the parent node, but keeps the KeyValues node #base, which is the root node of the file, and its children are all the children of the root node.

Example

/*
KeyValues.txt

#base "npc/file01.txt"
#base "npc/file02.txt"

"DOTAAbilities"
{
    "ability01"
    {
        "BaseClass"         "ability_datadriven"
        "AbilityBehavior"   "DOTA_ABILITY_BEHAVIOR_POINT"
    }
}
*/
const root = await KeyValues.Load(join(__dirname, 'KeyValues.txt'));

// Get path
baseList[0].GetBaseFilePath(); // npc/file01.txt
baseList[0].filename; // {__dirname}/npc/file01.txt

// Calling `Save` will automatically save the `#base` file
KeyValues.Save();

// Note that if other file path are specified, the base kv will be created together with the relative paths.
KeyValues.Save(join(__dirname, 'otherPath/KeyValues.txt'));

Create

// Create the root node, which is a static method
KeyValues.CreateRoot(): KeyValues

// Only create KeyValues when children exist, otherwise an error will be thrown
// Return new KeyValues
CreateChild(key: string, value: string | KeyValues[]): KeyValues

// Returns self, such as SetValue('a').GetValue()
SetValue(v: string | KeyValues[]): this

Example

/*
"Table"
{
    "Item"  "item_0001"
    "Item"  "item_0002"
}
*/

const root = KeyValues.CreateRoot();
const kv = root.CreateChild('Table', []);
kv.CreateChild('Item', 'item_0001');
kv.CreateChild('Item', 'item_0002');

// or

root.CreateChild('Table', [new KeyValues('Item', 'item_0001'), new KeyValues('Item', 'item_0002')]);

// or

kv.SetValue([new KeyValues('Item', 'item_0001'), new KeyValues('Item', 'item_0002')]);

Add / Delete

// Called only when children exist in KeyValues, otherwise an error will be thrown
// Add KeyValues to the end of children
Append(child: KeyValues): this

// Called only when children exist in KeyValues, otherwise an error will be thrown
// Add KeyValues to the specified location of children
Insert(child: KeyValues, index: number): this

// Delete the specified key or KeyValues from children
// Return deleted KeyValues
Delete(child: string | KeyValues): KeyValues | undefined

// This function is used to release the KeyValues and unlink the nodes,
// i.e. remove self from the parent node, and set the parent to undefined
Free(): this

Note that the KeyValues of SetValue, Append, Insert will change the parent

Example

const root = KeyValues.CreateRoot();
const kv = root.CreateChild('Table', []);
kv.Append(new KeyValues('Item', 'item_0001'));
kv.Append(new KeyValues('Item', 'item_0002'));

kv.Delete('Item')?.GetValue(); // item_0001

Find

// Find a KeyValues
Find(
    callback: (kv: KeyValues, i: number, parent: KeyValues) => boolean
): KeyValues | undefined

// Find multiple KeyValues
FindAll(
    callback: (kv: KeyValues, i: number, parent: KeyValues) => boolean
): KeyValues[]

// Find a KeyValues
FindKey(key: string): KeyValues | undefined

// Find multiple KeyValues
FindAllKeys(...keys: string[]): KeyValues[]

// Traversing the KeyValues tree
FindTraverse(
    callback: (kv: KeyValues, i: number, parent: KeyValues) => boolean
): KeyValues | undefined

Example

// For example, the above Table
kv.FindAllKeys('Item'); // [KeyValues('Item', 'item_0001'), KeyValues('Item', 'item_0002')]

Convert to Javascript Object

Easy to convert to JSON.

// Return an object
kv.toObject();

KeyValues3

Compared to KeyValues, KeyValues3 has multiple data types, a format similar to JSON, and relatively more complex code than KeyValues. The code is also much more complex than KeyValues.

Reference https://developer.valvesoftware.com/wiki/Dota_2_Workshop_Tools/KeyValues3

Features

  • Retain comments
  • Support Node.js and browser
  • Friendly data type inference

Import

import {
    KeyValues3,
} from 'easy-keyvalues';

KeyValues3.Load(file: string): Promise<KeyValues3>;
KeyValues3.Save(): Promise<void>;
KeyValues3.Save(otherFile: string): Promise<void>;

Usages

// Parse KeyValues3
const kv3 = await KeyValues3.Load('/path/to/file.txt');
console.log(kv3.toString());

Data Types

KeyValues3 Type Javascript Type Description
String string KV3 supports multi-line strings with """ as the beginning and end
Boolean boolean true or false
Int number integer
Double number When formatting as a string usetoFixed(6)
Array Array Array,Type:IKV3Value[]
Object Object Object,Type:KeyValues3[]
Feature string resource:"example" soundevent:"example"
FeatureObject Object subclass: {}

Note that when parsing Int and Double, they are only parsed as Double if they contain a fractional part, otherwise they are treated as Int

Root Node

The root node of KeyValues3 is a bit special in that it contains a file header and its value is fixed to Object, with the following basic format:

<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
}
  • Static property KeyValues3.CommonHeader is default header

  • Static method KeyValues3.CreateRoot(): KeyValues3 Create root node

  • Method GetHeader(): string | undefined Get file header

  • Method IsRoot(): boolean Determine if it is the root node

Create

// Called only when the value of KeyValues is Object, otherwise an error is thrown
// Create KeyValues3 to Object
// Return created KeyValues3
CreateObjectValue(key: string, value: IKV3Value): KeyValues3

KeyValues3 values are a class, the interface is IKV3Value and the base class is KV3BaseValue.

interface IKV3Value {
    Comments: KeyValues3Comments;
    GetValue(): any;
    GetOwner(): KeyValues3 | undefined;
    SetOwner(owner: KeyValues3 | undefined): void;
    IsBoolean(): this is ValueBoolean;
    IsInt(): this is ValueInt;
    IsDouble(): this is ValueDouble;
    IsString(): this is ValueString;
    IsFeature(): this is ValueFeature;
    IsFeatureObject(): this is ValueFeatureObject;
    IsArray(): this is ValueArray;
    IsObject(): this is ValueObject;
    Format(): string;
}
KeyValues3.String( initValue?: string )
KeyValues3.Boolean( initValue?: boolean )
KeyValues3.Int( initValue?: number )
KeyValues3.Double( initValue?: number )
KeyValues3.Array( initValue?: IKV3Value[] )
KeyValues3.Object( initValue?: KeyValues3[] )
KeyValues3.Feature( feature: string, value?: string )
KeyValues3.FeatureObject( feature: string, initValue?: KeyValues3[] )

Example

const root = KeyValues3.CreateRoot();
root.CreateObjectValue('a', KeyValues3.String('string'));
root.CreateObjectValue('b', KeyValues3.Boolean(false));
root.CreateObjectValue('c', KeyValues3.Int(0));
root.CreateObjectValue('d', KeyValues3.Double(0.0));
root.CreateObjectValue('e', KeyValues3.Array([]));
root.CreateObjectValue('f', KeyValues3.Object([]));
root.CreateObjectValue('g', KeyValues3.Feature('resource', 'path/to/file.vpcf'));
root.CreateObjectValue(
    'h',
    KeyValues3.FeatureObject('subclass', [new KeyValues3('child', KeyValues3.String('value'))]),
);

KeyValues3.Array([KeyValues3.String('one'), KeyValues3.String('two'), KeyValues3.String('three')]);

const obj = KeyValues3.Object([
    new KeyValues3('a', KeyValues3.String('one')),
    new KeyValues3('b', KeyValues3.Int(2)),
]);
obj.Create('c', KeyValues3.Boolean(true));

Add / Delete

  • KeyValues3 Object
// Append to the end of Object
Append(...values: KeyValues3[]): this
// Insert into Object at the specified location
Insert(index: number, ...values: KeyValues3[]): this
// Deletes KeyValues3 from the child node and returns the deleted KeyValues3
Delete(v: string | KeyValues3): KeyValues3
  • KeyValues3 Array
// Append to the end of Array
Append(...values: IKV3Value[]): this
// Insert into Array at the specified location
Insert(index: number, ...values: IKV3Value[]): this
// Delete the IKV3Value in the child node
Delete(v: IKV3Value): this

Find

  • KeyValues3 Object
// Find a KeyValues3
Find(
    callback: (kv: KeyValues3, i: number, parent: ValueObject) => boolean
): KeyValues3 | undefined

// Find a KeyValues3
FindKey(key: string): KeyValues3 | undefined

// Find multiple KeyValues3
FindAll(
    callback: (kv: KeyValues3, i: number, parent: ValueObject) => boolean
): KeyValues3[]

// Find multiple KeyValues3
FindAllKeys(...keys: string[]): KeyValues3[]

These methods are also present in the KeyValues3 method and can be called when the value is an Object

Convert to Javascript Object

Easy to convert to JSON.

// Return an object or array
kv3.toObject();

Format

KeyValues3 Type Javascript Value Format after
String this is string "this is string"
String this is string \n second line ””“
this is string
second line
””“
Boolean true true
Int 5 5
Double 2.5 2.500000

Custom adapter

The library is already adapted for nodejs, but due to the complexity of the browser environment, no browser adapters are provided, you can refer to src/node.ts and src/adapter.ts for adaptations.

About ID

Both KeyValues and KeyValues3 support the ID property, which is provided by createKeyValuesID() in the adapter and by default returns the empty character If you need this ID, just rewrite createKeyValuesID(). The ID exists to support cross-threaded operation like scenario.

License

MIT License