Skip to content

property_parser

TeamSpen210 edited this page Aug 28, 2017 · 1 revision

Provides the Property class, which parses and manipulates most KeyValues files. Unlike nested dicts, Property trees remember order, and also permit multiple duplicate names.

Examples will be given using the following keyvalues file:

"Head1"
	{
	"Key" "Value"
	"IntKey" "47"
	"Boolean" "true"
	"Subkey"
		{
		"Name" "Hello"
		"Name" "World"
		}
	}

##Attributes There are two kinds of Properties - leaf properties, which have a simple string value, and block properties, which contain a block of sub-properties. In general operations done on blocks cannot be done on leaves, and vice versa. Property objects have 3 main attributes.

  • name: The name of the property, in lowercase. Set this to change real_name as well. When read, the name will be casefold()ed.
  • real_name: The name in original capitalised form. Treat as read-only.
  • value: The property value for a leaf property. For blocks, this shouldn't be read directly.

To parse a keyvalues file, pass either an iterable of lines (file-like object), or the entire text to Property.Parse(). Additionally it is recommended to pass a filename, which will be shown in error messages.

>>> with open('props.txt') as f:
...     props = Property.parse(f, 'props.txt')

Alternatively you can directly construct a Property, by passing a name and value to the constructor. For blocks the value should be a list of Property.

>>> props = Property(None, [
...     Property("Head1", [
... 	    Property("Key", "Value"),
... 	    Property("IntKey", "47"),
... 	    Property("Boolean", "true"),
... 	    Property("Subkey", [
... 		Property("Name", "Hello"),
... 		Property("Name", "World"),
...         ])
... 	])
... ])

A special case is a Property with a name of None. This is simply a container to hold the top-most properties - since it is possible for a file to contain multiple top-most properties. It is used so the various methods can be used to retrieve subkeys.

To produce a text file back from a tree, iterate over the lines produced by Property.export():

>>> with open('out.txt', w) as f:
...     for line in props.export():
...         f.write(line)

If run on a None Property, the children will be written to the file one after each other.

Exceptions:

Two exceptions are defined for this module.

  • KeyValError: Raised when encountering an invalid input file. The mess attribute is the human-readable message. filename is the filename given to Property.parse(). line_num is the line number for the error, if appropriate.
  • NoKeyError: Raised if the given key is not found in a property tree. The key attribute is the key which is being looked for.

Searching the tree

There are several methods of searching property trees for specific values. Note that searching is case-insensitive, and returns the last matching property; this allows previous properties to be overwritten by later values.

Iteration:

Iterating a property block simply returns each sub-property in turn.

"name" in tree:

Containment testing checks if the given name matches a child.

tree.find_all(*keys):

Yields each Property object which matches a path of keys. tree.find_all('Head1', 'Subkey', 'Name') will yield each Name property. Items are returned in the order they would appear in a file.

tree.find_children(*keys):

Yields the children successively from Property objects matching the keys. Equivalent to:

for child in tree.find_all(*keys):
	yield from child

tree.find_key(name[, default]):

Returns the last sub-key which matches the given name. If none are found and no default is provided, this raises NoKeyError. If a default is provided, a new Property is constructed with the name and default and returned.

tree[name], tree[name, default]:

This does the same thing as find_key(), except it returns the string value of the property. A default value can also be provided as shown (which can be any object).

tree.int(name, def_=0)

tree.bool(name, def_=False)

tree.float(name, def_=0.0)

tree.vec(name, x=0.0, y=0.0, z=0.0):

Convenience functions. These do the same thing as find_key(), but then attempt to parse the value into the specified type. If it is not valid, or the key doesn't exist the default is returned.

tree.iter_tree(blocks=False):

Iterate over every sub-property of this one, in lexigraphical order. If blocks is True, it will return the blocks in addition to the leaf Properties.

Modifying values

Several methods are available to edit values. The simplest method is to directly change the name or value of a property you have a reference to.

tree.clear():

Deletes all the sub-properties.

tree.copy():

Creates an independent deep copy of this tree and all its children. Since the tree is mutable, this is necessary if it is undesirable to edit the original tree.

tree.append(prop), tree += prop

Add proprerties to the end of this one. prop can be a list of properties, or a single property.

tree1 + tree2:

Deep-copies both property trees, then joins them together.

tree[name] = value:

Sets the last leaf with the given name to a value. Similar to tree.find_key(name).value = value, but if the property is not present one will be created and appended to the end.

tree.set_key(path, value):

tree[path_1, path_2, path_3,...] = value

Sets a value on a key deep in a hierachy. The path is a tuple of key names for the method call, or directly for the [] method. For each name, a block with that name is found inside the previous one (or creared and added if not found). The last key in the path is set to the specified value.

For example, examp.set_key(('Head', SubKey', 'Name'), 'Galaxy') will change "World" to "Galaxy".

tree.ensure_exists(name):

If a block with the given name exists, it will be returned. Otherwise an empty block will be created, appened to the end and then returned.

del tree[name]:

Delete the last property with a value.

tree.merge_children(*names):

Merge together the children with the given names. This ensures only one blocks with each of the passed names exists (merging together the original children). The new blocks will be moved to the end of the tree. No block will be added if the given name is not present.

Conditional Properties

Some of Valve's keyvalues files contain special flags specified after a property, to conditionally enable or disable it on specific platforms:

"Block"
	{
	"Platform" "Computer" [PC]
	"Platform" "Not Console" [!GameConsole]
	"Platform" "Playstation 3" [ps3]
	}

This is supported via a flags parameter in Property.Parse(). This should be a dict mapping flag names to True or False values. By default, the following flags are set with the following values:

  • gameconsole = False: We assume this is not run on console.
  • x360 = False
  • ps3 = False
  • win32 = True: This is actually true for any PC OS.
  • osx: True if on OS X (sys.platform.startswith('darwin')).
  • linux True if on Linux (sys.platform.startswith('linux')).