easy-keyvalues
Version:
Parse Valve KeyValues Format and easy to use in nodejs or browser.
455 lines (338 loc) • 14.3 kB
Markdown
# easy-keyvalues
[](https://github.com/RobinCodeX/easy-keyvalues/actions/workflows/main.yml)
[](https://github.com/RobinCodeX/easy-keyvalues/actions/workflows/codeql-analysis.yml)
[](https://www.npmjs.com/package/easy-keyvalues)
[](#keyvalues)
[](#keyvalues3)




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.
[简体中文](https://github.com/RobinCodeX/easy-keyvalues/blob/master/README-zh-cn.md)
> I completely rewrote the library to not be compatible with previous versions, so the version
> starts from 1.0.0.
## Installation
```shell
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
```ts
import {
KeyValues,
} from 'easy-keyvalues';
KeyValues.Load(file: string): Promise<KeyValues>;
KeyValues.Save(): Promise<void>;
KeyValues.Save(otherFile: string): Promise<void>;
```
## Usages
```ts
// 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:
```js
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
```js
/*
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
```js
// 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
```js
/*
"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
```js
// 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
```js
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
```js
// 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
```js
// 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.
```js
// 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
```ts
import {
KeyValues3,
} from 'easy-keyvalues';
KeyValues3.Load(file: string): Promise<KeyValues3>;
KeyValues3.Save(): Promise<void>;
KeyValues3.Save(otherFile: string): Promise<void>;
```
## Usages
```ts
// 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 use`toFixed(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
```js
// 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`.
```ts
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;
}
```
```js
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
```js
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
```js
// 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
```js
// 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
```js
// 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.
```js
// 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` | """<br>this is string<br>second line<br>""" |
| 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](./LICENSE)