-
Notifications
You must be signed in to change notification settings - Fork 0
Qonfig
Qonfig is a toolkit for creating a certain flavor of strictly-validated, hierarchical data structure from hierarchical data, especially XML.
Qonfig allows other APIs to specify toolkits which perform strict yet flexible validation of input and produce a structure that is easy to interpret.
It differs significantly from other XML validation methods such as XSL in that it is more flexible (though just as strict) on how attributes and values are allowed. This is done via add-ons. Add-ons, like element definitions, specify attributes and child elements that may be specified by elements in Qonfig Documents which inherit them, but Qonfig offers a great deal of power to determine how and when add-ons are inherited by an element.
Specifically, add-ons may be inherited by a document element due to auto-inheritance or by value.
A QonfigToolkit is a specification by an API that describes structures for the API. It consists of:
- Types
- Elements
- Add-Ons
- Auto-Inheritance A toolkit may depend on other toolkits and use their structures in its own. It may even modify them in some ways.
A toolkit may define a root element type. The root element of a document must be a type of a root declared in one of its used toolkits or extend one.
A Qonfig Type is a type that validates and parses simple text values in the source document, e.g. XML attributes and element text content. The available types are:
- string: Performs neither validation nor parsing, but passes the input text through to the result document as-is
- boolean: Requires either "true" or "false" and passes the value to the result as a boolean type
- pattern: Takes a java-flavor regular expression pattern and uses it to validate parse values. Passes the Matcher to the result.
- literal: Requires an exact value. Often used with the one-of type.
- one-of: Allows values to be validated by any of several component types. The first specified component type that matches a value is used to parse it.
- explicit: Watches for an optional prefix and/or suffix and parses the core text to a component type
- external: Allows toolkits to supply their own value parsers
A Qonfig Element is a type of which an instance can be created in a document by name. It is defined in a Qonfig Toolkit and may be thought of as a class of which each element in a Qonfig Document is an instance.
A Qonfig Element may specify:
- An optional single super element to extend. If specified, the sub-element will inherit all the super-element's attributes, child types, and its value.
- Inheritance. This is a list of add-ons whose attributes and children will be inherited by the element.
- Abstract (boolean). An abstract element cannot be declared in a document by that type. Other non-abstract elements may extend the element and they may be declared in documents.
- Child, attribute, or value modification. Components inherited from the super-element or inherited add-ons may be modified in certain ways. See the component type descriptions below for details on how each may be modified.
- Attributes Attributes which may be specified on the element
- Value The text value which may be specified on the element
- Children Child elements which may be specified on the element
An add-on is a structure which modifies what attributes and children may be specified on an element. In the analogy of a Qonfig Element being a class and the element in the Document being the instance, add-ons may be thought of as Java interfaces. An element in a document may inherit any number of add-ons, each of which adds new attributes and/or children that may/must be specified, or which modify the types or other features of inherited attributes or children.
Add-ons may be inherited by document elements in several ways, some of which are unlike Java interface inheritance.
- Specification on the element definition. As noted above, Qonfig Elements may specify add-ons to inherit. Any add-ons specified for an element are also inherited by elements which extend the element.
- As fulfillment of a role. By nature of a document element being directly under a particular parent element and fulfilling some role under it, the role may cause the element to inherit a set of add-ons.
- Auto-inheritance. A toolkit's auto-inheritance may cause an add-on to be inherited by any of several conditions. See Auto-Inheritance for more information.
- By value. An attribute or element value whose type is an add-on will cause the element it is specified on to inherit the specified add-on.
Add-ons may specify:
- An optional single required element. If specified, it will be forbidden for the add-on to apply to an element whose type does not extend the given element.
- Inheritance. This is a list of add-ons whose attributes and children will be inherited by the add-on.
- Abstract (boolean). An abstract add-on may not be inherited by an element in a document by name and cannot be given as an add-on-typed attribute value, but must be inherited by type from a toolkit. Only abstract add-ons can be specified as a requirement for a child role.
- Child, attribute, or value modification. Components inherited from the required element or inherited add-ons may be modified in certain ways. See the component type descriptions below for details on how each may be modified.
- Attributes Attributes which may be specified elements which inherit the add-on
- Children Child elements which may be specified elements which inherit the add-on Because elements can inherit multiple unrelated add-ons, add-ons may not specify a value, though they may modify that inherited from their required element.
However add-ons are inherited by a document element, all types inherited by the element must be compatible. All the constraints for each attribute,, element value, and child by each element and add-on must be compatible or an error will be thrown at the earliest stage at which it can be determined. See each component type for more details.
As mentioned, one of the most powerful features of the Qonfig toolkit and the reason for its creation is the extremely flexible ability to control what attributes and children are available based on various conditions. One of these mechanisms is auto-inheritance.
Auto-inheritance is specified in a Qonfig Toolkit. Each auto-inheritance component consists of a set of conditions and an add-on or list of add-ons that will be inherited by elements that match all the conditions. Each condition consists of:
- An element type. A Qonfig Element or add-on that a document element must declare, extend, or inherit to meet the condition.
- A child role. A role that the document element must fulfill to meet the condition.
This mechanism has several uses. Some examples:
- In the Expresso API, the Expresso-Base toolkit modifies the <value> element specified by the Expresso-Core toolkit so that <value>s specified under an internal <model> element can specifiy an init attribute.
- In the Expresso API, the Quick-Base toolkit modifies the root <quick> element specified by the Quick-Core toolkit to allow the specification of a menu bar.
A Qonfig Attribute is a type that allows a value to be specified on an element in a Qonfig Document by name. Attributes specify:
- Name: The name by which the attribute may be specified
- Type: A reference to a Qonfig Type by name, or the name of an add-on
- Specification: Whether the attribute is required, optional, or forbidden. The latter specification is typically used for modifying inherited attributes.
- Default value: If the specification is optional or forbidden, a default value may be specified which will be present in the parsed document if it is not otherwise specified in the source document. This may not be specified if the attribute's specification is required.
Attributes inherited from a super type may be modified by a sub-type. The nature of the modification must be compatible with the requirements specified by the super type.
- The type may be extended. If the inherited attribute's type is an add-on, the type may be modified by a sub-type to require a particular extension of that of the inherited attribute.
- The specification may be modified. There are constraints to this though:
- If the inherited attribute's specification is forbidden, it cannot be modified
- If the inherited attribute's specification is required, a default value must be provided by the modification so the inherited attribute always has a value, as specified.
- A default value may be modified (if the specification is not required)
As mentioned in the add-ons section above, all attributes inherited by an element must be compatible. The only way attributes can be made incompatible is by their specification. For example:
Say add-on-a specifies some optional attribute. add-on-b inherits add-on-a and changes its specification to required. add-on-c also inherits add-on-a and changes the specification to forbidden and does not provide a default value. All of this is allowed.
Now, however, if a toolkit declares an element definition or add-on that inherits both of these either directly or indirectly, an error will be thrown parsing or assembling the toolkit, since these are incompatible. The add-on-b type expects a value for the attribute, but add-on-c forbids it to be specified in a document and provides no default value.
Similarly, if an element in a document inherits both of these in some way (e.g. via value or declared inheritance), an error will be thrown parsing or assembling the document.
Auto-inheritance also is checked for compatibility, both when assembling the toolkit, and when applying the inheritance to a document.
Keep in mind that an attribute is not identified by its name. Therefore a Qonfig Element definition or a document element may safely inherit multiple unrelated add-ons that specify attributes with the same name. In this case, during parsing the attribute should be qualified by the element or add-on that declared the attribute.
A Qonfig value validates and parses the single text value allowed for each element in a Qonfig Document. Its content, modification, and compatibility are identical to that of Qonfig Attributes with 2 differences:
- A value has no name, and may only be specified once on each [Qonfig Element](#Qonfig Element) definition
- A value cannot be specified by a Qonfig Add-On, since an element may inherit many add-ons and this would often cause conflicts.
A Qonfig child is a type that allows elements to be specified under another element in a Qonfig Document. A child must specify:
- Name: The name of the role. This only needs to be referenced in the document if there are multiple child roles that a given child element may fulfill. Its most frequent use is in retrieving the children of an element in a document by role (see the interpretation section.
- Type: A reference to a Qonfig Element definition that the child must declare or extend
- Requirement: An abstract add-on or set of abstract add-ons that a child must inherit in order to fulfill this role. These must be compatible with the child's type (given each add-on's requirement).
- Inheritance: An add-on or set of add-ons that elements that fulfill this role will automatically inherit. These also must be compatible with the child's type.
- Min/Max: The minimum and maximum number of elements of a particular role that may be given per element. Min must, of course, be <= max.
Children inherited from a super-type may be modified by a sub-type. As with attributes and element values, the nature of the modification must be compatible with the requirements specified by the super type.
- The type may be extended. A type that is extends that given by the inherited child may be required instead.
- Additional required add-ons may be specified. These must be compatible with the child's type (given each add-on's requirement).
- Additional inherited add-ons may be specified. These also must be compatible with the child's type.
- The min/max may be changed, respecting the inherited constraints: the modified min and max must be within the inherited min/max range. The modified minimum must, of course, be <= the modified maximum.
Child roles must be compatible between all inherited types. There are many ways that children can be made incompatible:
- By type: Say add-on-a declares a child with type element-a. If add-on-b modifies the type to element-b (which extends element-a) and add-on-c modifies it to element-c (which also extends element-a), it is impossible that an element could fulfill both of these roles, since an element can only extend a single element definition.
- By requirement: This functions the same way. If a child is modified by 2 inherited add-ons to require add-ons which themselves require elements that are compatible with the child's type but not with each other, those 2 add-ons will be incompatible.
- By inheritance: This works the same way as the requirement.
- Number: If 2 inherited add-ons modify the minimum/maximum of the inherited child such that the 2 ranges do not overlap, the add-ons are incompatible.
An attribute or element value may specify an add-on as its type. When providing a value for the attribute in a Qonfig Document, an add-on that inherits that type (or the type add-on itself, I suppose) must be given. In this case, the add-on specified will be inherited by the element, such that all of that add-on's attributes and children may/must be specified, and any modifications. This may cause an error if the given add-on is incompatible with other add-ons the element inherits.
An example of this is in the Quick-Base toolkit of the Expresso API, where the <box> element (a simple container type) requires the specification of the "layout" attribute, with a type of the "layout" add-on. "layout" is abstract. The Quick-Base toolkit provides multiple layout implementations, including "inline-layout" which itself declares a required "orientation" attribute and 2 optional attributes. One would then specify a box in a Quick document like so:
<box layout="inline-layout" orientation="horizontal" main-align="justify">
...[content]...
</box>
The orientation and main-align are defined by the inline-layout add-on, but they are given on the box element, which is caused to inherit inline-layout by specifying it as the value of the layout attribute.
Element types and add-ons in toolkits may be defined with metadata in the form of Qonfig-validated elements, as in a document.
An element type or add-on may declare that metadata may be specified under element types that extend/inherit the type. This means that extensions of the type may specify Qonfig-validated elements under the declaration of the element.
An example of this is the <with-element-model> add-on in the Expresso-Core toolkit of the Expresso API. It declares metadata of type <element-model>, which allows extensions of this type to declare values that must will be available in the element's models and must be specified by the element's implementation.
The focus of the Qonfig API is the QonfigDocument. A QonfigDocument is a structure, parsed from XML, that is validated and formatted according to the specifications of one or more QonfigToolkits.
As noted above the type of the root element must be one of those specified as a root in a used toolkit or extend one.
Each element in the document declares as its type a Qonfig Element declared in a toolkit. Child elements must fulfill at least one role in the parent element type (or one of its inherited add-ons). The role may be inferred based on the element type if and only if there is exactly one child on the element that is compatible with the type. Otherwise the role or roles must be declared.
The resulting parsed structure contains all the attributes, element values, and children of the root element, stored in a way that is suitable for interpretation.
The Qonfig API is not inherently dependent on the XML language, and it is certainly possible to create Qonfig parsers for other languages. It is also, of course, possible to construct Qonfig documents programmatically without any text input. The API does however, bear great similarities in structure to XML, and the typical usage of Qonfig is to parse toolkits and documents from XML files.
A Qonfig Toolkit XML file contains a root <qonfig-def> element with:
- A "name" attribute, the name of the toolkit as referred to from other toolkits or documents
- A "version" attribute, the version of the toolkit as referred to from other toolkits or documents. It consists of a major and a minor version integer, structured as "v?\d+.\d+".
- Any number of
extends:[ref]="Toolkit-Name vToolkit-Version"
attributes, defining the toolkits that this toolkit depends on. - An optional <value-types> element, containing any number of type declarations, each of which has a "name" attribute (by which it may be referred to from attribute and element value types). See Qonfig Types for more information on each type. Each element may be:
- <string>
- <boolean>
- <pattern>. The value of this element is the regex pattern to use.
- <literal>. The value of this element is the literal text that must be specified.
- <one-of>. This element must contain one or more other type elements with no name.
- <explicit>. This element must have a "prefix" attribute, a "suffix" attribute, or both. It must contain a single type element with no name.
- <external>
- An optional <add-ons> element, each child element of which must be named "add-on" and defines an add-on:
- A "name" attribute specifies the name of the add-on
- An optional "requires" attribute which refers to an element definition in the toolkit or one of its dependencies.
- An optional "inherits" attribute, which refers to an add-on or a comma-separated list of add-ons in the toolkit or one of its dependencies.
- An optional "abstract" attribute which may be "true" or "false"
- Any number of declared attributes, children, or attribute/child/value modifiers, which are described below.
- An optional <elements> element, each child element of which must be named "element-def" and defines a Qonfig element type:
- A "name" attribute specifies the name of the add-on
- An optional "extends" attribute which refers to an element definition in the toolkit or one of its dependencies.
- An optional "inherits" attribute, which refers to an add-on or a comma-separated list of add-ons in the toolkit or one of its dependencies.
- An optional "abstract" attribute which may be "true" or "false"
- Any number of declared attributes, children, or attribute/child/value modifiers, which are described below.
- Any number of <element-meta> elements whose attributes are the same as that of the <child-def> element below, except with no name
- An optional <auto-inheritance> element, each child element of which must be named "auto-inherit" and defines an auto-inheritance:
- The "inherits" attribute refers toan add-on or a comma-separated list of add-ons in the toolkit or one of its depdendencies.
- One or more <target> elements with:
- An "element" attribute which refers to an element or add-on type or comma-separated list thereof
- A "role" attribute which refers to a child role declared by an element or add-on.
New attributes are defined by an <attribute> element with:
- A "name" attribute
- A "type" attribute referring by name to a value type or an add-on declared in the toolkit or one of its dependencies.
- A "specify" attribute that may be "required", "optional", or "forbidden". Default is "required".
- An optional "default" attribute, which is parsed according to the attribute's type
An element value is defined on an element definition by a <value-def> element with the same content as an <attribute> element except for the "name" attribute.
New children are defined by a <child-def> element with:
- A "name" attribute, the name of the role being defined
- An optional "type" attribute, a reference to an element definition in the toolkit or one of its dependencies. Type is almost always specified, but if it is not, then it may be fulfilled by any element type (as long as the "requires" add-ons are inherited).
- An optional "role" attribute, which is a reference to a child role or comma-separated list of child roles defined by an inherited element type or add-on.
- An optional "requires" attribute, a reference to an abstract add-on or comma-separated list of add-ons defined in the toolkit or one of its dependencies.
- An optional "inherits" attribute, a reference to an add-on or comma-separated list of add-ons defined in the toolkit or one of its dependencies.
- Optional "min" and/or "max" attributes, as integers. Min and max both default to 1.
Inherited attributes may be modified by an <attr-mod> element with the same:
- A "name" attribute, referring to an attribute specified by an inherited element type or add-on.
- The same definition attributes as the <attribute> element. Any that are not specified are inherited.
Inherited values may be modified similarly to attributes by a <value-mod> element, but with no name.
Inherited children may be modified by a <child-mod> element with:
- A "child" attribute, referring to a child role specified by an inherited element type or add-on.
- The same definition attributes as the <child> element. Any that are not specified are inherited.
References to element types or add-ons may be by name if:
- The type is declared in the same toolkit as the reference
- Or only a single type with the given name is specified by the toolkit or any of its dependencies Otherwise, the type must be qualified by the reference name of the toolkit as declared in the "extends:[ref]" attribute on the root <qonfig-def> element. E.g. "dependency.element-name". Types from toolkits that are dependencies of dependencies may be referenced by the reference name of the top-level dependency if there is only a single type with the given name available to that dependency. Otherwise, the sub-dependency will need to be declared as a top-level dependency on the toolkit and given a reference name.
References to attributes or children may be by name if there is only a single attribute/child with that name declared by any element type or add-on inherited by the type or document element. Otherwise the attribute/child must be qualified by the name of the type that declared it (or any type that inherits that type, as long as there is only a single attribute/child with that name on the qualifier). The qualifier type itself may or may not need to be qualified by toolkit reference name.
A Qonfig Document XML file contains a root element whose name is a reference to an element definition in one of the used toolkits. In addition to any attributes specified by that type and any inherited add-ons, it needs a uses:[ref]="Toolkit-Name vToolkit.Version"
attribute for each toolkit utilized.
In addition to any attributes specified by the element's type and any inherited add-ons, each element in a document may specify a "with-extension" attribute whose value is a reference to a non-abstract add-on or a comma-separated list of add-ons from a utilized toolkit.
References to types, attributes, and children work similarly as in toolkit parsing.
All element types, add-ons, attribute/value/child declarations, and document elements may be documented with a description of the item. This is accomplished with a <?DOC > processing instruction whose content is the text description. This instruction does not affect the structure or functionality of the toolkit or document, but provides a description which is passed along into the parsed toolkit and document structures.
Qonfig is a very useful API for validating input, but the output, a QonfigDocument is not by itself very easy to use. The structure contains many methods for retrieving attributes and children by name, for example, and this may be sufficient for many applications in which the set of toolkits required by the API for parsing input is known and fixed. However, if there is any possibility of utilizing an API with other, unrelated toolkits, these methods may be insufficient, since other toolkits may declare types, attributes, and children with the same name as those in the toolkits known.
To address this difficulty and provide further utility, a powerful Qonfig-specific interpretation architecture is provided. Qonfig interpretation provides an interpreter that can be configured to take a QonfigDocument structure and output any custom structure.
To create the interpreter, the QonfigInterpreterCore.build() method is invoked to create a builder. The parameters to this method are:
- The calling class. This can allow interpretation to use classpath resources of the caller.
- ErrorReporting, which allows errors, warnings, and other messages to be reported with a reference to the location in the file of the XML structure the message is relevant to.
- All toolkits used by the parsed document
The builder may then be configured with:
- Special session implementations. The interpreter may be transformed into an instance of one of these sessions via the AbstractQIS.as(Class) method. Special session implementations may provide custom functionality and handling of variables.
- Value creators. The createWith() methods provide the interpreter with the ability to create a value of a specified type for a specified element or add-on type.
- Value modifiers. The modifyWith() methods cause the interpreter to modify any value created for the given Java and Qonfig type.
The build() method will then produce a QonfigInterpreterCore. The interpret(QonfigElement) method of this produces a CoreSession, an implementation of AbtractQIS (standing for Qonfig Interpretation Session, which is way too long to muck up my source files with). Upon this:
- The interpret(Class) method may be called to interpret the element represented by the session, producing a value of the given type.
- The asElement() methods produce a session whose focus type is the given element. Unlike when dealing with the raw QonfigDocument, retrieving attributes and children through a session is safe because the session knows which Qonfig type caller cares about. The creator and modifier callbacks configured in the session receive AbstractQIS parameter values whose focus type is that configured for the creator/modifier. When retrieving child sessions the sessions are configured to know all types that the caller can assume are applicable to the given child.
- The getAttribute() methods may be called to retrieve attributes by name.
- The getChildren() method returns child elements by role name.
- The forChildren() methods return child elements (either all or by role name).
- The interpretChildren() method interprets children of the given role as the given type.
- The reporting() method provides the error reporting for the session's element. Information sent to the reporting is reported along with the relevant file location.
- The values() provides a set of values stored for the session by name key. Values can be put into the session to be visible either only to sessions for that element, or to be visible to sessions for that element and all of its descendants, until another value in a descendant overrides it.
There are also methods for querying [element metadata](#Qonfig Metadata) and source file position information
Interpretation is easy to use, because you simply ask it for an instance of the type you require. The callbacks in turn ask interpretation for attribute values and child values interpreted to their own custom types, assemble an object of the desired type, and return it.
The power of the Qonfig API comes with a lot of code required to configure parsing, parse all the required toolkits and documents, configure the interpreter, and interpret the result. To manage this, the QonfigApp tool is provided.
QonfigApp is a class that defines a Qonfig toolkit that represents a Qonfig application. Instead of writing code to perform all the required configuration, one may write a Qonfig-validated XML file defining the configuration.
QonfigApp configuration consists of a root <qonfig-app> element which uses the Qonfig-App toolkit provided in the source, and contains:
- An "app-file" attribute that is the location of the Qonfig file representing the target application.
- At least one <toolkit> element, defining the Qonfig toolkits used by the application
- The "def" attribute is the location of the toolkit XML file defining the toolkit
- Any number of <value-type> elements whose value is the class name of a CustomValueType implementation required by name by the toolkit.
- Any number of <special-session> elements whose value is the class name of a SpecialSessionImplementation implementation that is needed by interpreter implementations.
- At least one <interpretation> element, whose value is the name of a QonfigInterpretation implementation that accepts an interpreter builder and populates it with creation and modification of types needed for the application.
QonfigApp is called from code via the parseApp(URL, URL...) method. The first argument is the location of the qonfig-app file, the others are references to other toolkits that the qonfig-app file may make use of.