|
module.xml - New features
As we have a lot of features to integrate in module.xml I think we should move to a more strict syntax less prone to errors.
I'd like to have a module.xml that can be validated against a W3C XML Schema to strictly reduce the possibility of errors. XML Schema Validation is widely implemented, even PHP has its own sets of classes to validate a XML documents against a Schema. This way, for example, the module publishing script can validate the XML before publishing the module.
In addition I found some little issues with the actual module.xml:
- Operators: comparison operators (>=, <=, =, ecc...) are not allowed in XML (<= breaks XML parsing)
- Module element: 'module' element contained inside 'depends' element is different from root module element, this leads to validation and maybe parsing problems
Here is my proposal to address this kind of issues and to allow a strict automatic validation against an XML Schema
module.xml example:
<module rawname="timeconditions" name="Time Conditions" version="1.0.1" type="setup" category="Basic" location="release/timeconditions-1.0.tgz" md5sum="9dfd77d926e155de5568ec72b85a081a"> <info url="http://freepbx.org/wiki/TimeConditions" type="userdoc"/> <menuitems> <menuitem key="timeconditions" name="Time Conditions"/> </menuitems> <dependencies> <freepbx version="2.1" requirement="gt"/> <reqmodules combination="and"> <reqmodule rawname="core"/> <reqmodule rawname="ivr" version="1.0"/> <reqmodule rawname="findmfollow" version="2.0.0" requirement="le"/> </reqmodules> <engines combination="or"> <engine name="asterisk" version="1.2" requirement="ge"/> <engine name="openpbx" version="1.0"/> </engines> <files combination="and"> <file name="/etc/passwd"/> </files> </dependencies> <changes> <change version="1.1" description="Now supports easter calculations"/> <change version="1.1" description="Fixed bug #234567 and #3456789"/> <change version="1.0.1" description="Fixed bug #123456"/> </changes> </module>
As you can see I used an attributes based XML structure, I think it's easier to read and parse in our situation
Here are the main changes from the actual version that needs explanation
- requirement attribute of freepbx, engine, reqmodule
- The operator used to compare version numbers, valid operators are: eq, ne, gt, lt, ge, le (=, !=, >, <, >=, <=)
- combination attribute of files, engines, reqmodules
- The boolean operator used to combine requirements ('or', 'and')
Feel free to edit and comment this page and to take just some ideas to fix the minor issues described above.
Hope it helps !!
Finally here is the XML Schema you can use to validate the previous XML.
module.xsd:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <xs:complexType name="changeType"> <xs:attribute name="version" type="xs:string" use="required"/> <xs:attribute name="description" type="xs:string" use="required"/> </xs:complexType> <xs:complexType name="changesType"> <xs:sequence> <xs:element name="change" type="changeType" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:complexType name="dependenciesType"> <xs:sequence> <xs:element name="freepbx" type="freepbxType"/> <xs:element name="reqmodules" type="reqmodulesType" minOccurs="0"/> <xs:element name="engines" type="enginesType" minOccurs="0"/> <xs:element name="files" type="filesType" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="engineType"> <xs:attribute name="name" type="xs:string" use="required"/> <xs:attribute name="version" type="xs:string" use="optional"/> <xs:attribute name="requirement"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="eq"/> <xs:enumeration value="ne"/> <xs:enumeration value="gt"/> <xs:enumeration value="lt"/> <xs:enumeration value="ge"/> <xs:enumeration value="le"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="enginesType"> <xs:sequence> <xs:element name="engine" type="engineType" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="combination" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="and"/> <xs:enumeration value="or"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="fileType"> <xs:attribute name="name" type="xs:string" use="required"/> </xs:complexType> <xs:complexType name="filesType"> <xs:sequence> <xs:element name="file" type="fileType" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="combination" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="and"/> <xs:enumeration value="or"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="freepbxType"> <xs:attribute name="version" type="xs:double" use="required"/> <xs:attribute name="requirement" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="eq"/> <xs:enumeration value="ne"/> <xs:enumeration value="gt"/> <xs:enumeration value="lt"/> <xs:enumeration value="ge"/> <xs:enumeration value="le"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="infoType"> <xs:attribute name="url" type="xs:string" use="required"/> <xs:attribute name="type" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="userdoc"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="menuitemType"> <xs:attribute name="key" type="xs:string" use="required"/> <xs:attribute name="name" type="xs:string" use="required"/> </xs:complexType> <xs:complexType name="menuitemsType"> <xs:sequence> <xs:element name="menuitem" type="menuitemType" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:element name="module"> <xs:complexType> <xs:sequence> <xs:element name="info" type="infoType"/> <xs:element name="menuitems" type="menuitemsType" minOccurs="0"/> <xs:element name="dependencies" type="dependenciesType" minOccurs="0"/> <xs:element name="changes" type="changesType" minOccurs="0"/> </xs:sequence> <xs:attribute name="rawname" type="xs:string" use="required"/> <xs:attribute name="name" type="xs:string" use="required"/> <xs:attribute name="version" type="xs:string" use="required"/> <xs:attribute name="type" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="setup"/> <xs:enumeration value="tool"/> </xs:restriction> </xs:simpleType> </xs:attribute> <xs:attribute name="category" type="xs:string" use="required"/> <xs:attribute name="location" type="xs:string" use="required"/> <xs:attribute name="md5sum" type="xs:string" use="required"/> </xs:complexType> </xs:element> <xs:complexType name="reqmoduleType"> <xs:attribute name="rawname" type="xs:string" use="required"/> <xs:attribute name="version" type="xs:string"/> <xs:attribute name="requirement"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="eq"/> <xs:enumeration value="ne"/> <xs:enumeration value="gt"/> <xs:enumeration value="lt"/> <xs:enumeration value="ge"/> <xs:enumeration value="le"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="reqmodulesType"> <xs:sequence> <xs:element name="reqmodule" type="reqmoduleType" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="combination" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="and"/> <xs:enumeration value="or"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> </xs:schema>
Here is a little PHP code snippet used to validate an XML against a Schema
libxml_use_internal_errors(True);
$dom = new DomDocument();
$dom->strictErrorChecking = TRUE;
# Well-formedness
$valid = @$dom->load('module.xml');
if (!$valid) { // not well-formed
$errors = libxml_get_errors();
foreach ($errors as $error) {
print display_xml_error($error);
}
libxml_clear_errors();
} else {
# Schema Validation
$valid = @$dom->schemaValidate('module.xsd');
if (!$valid) { // Not validated
$errors = libxml_get_errors();
foreach ($errors as $error) {
$html .= display_xml_error($error);
}
libxml_clear_errors();
} else $valid = True; // Validated
}
function display_xml_error($error) {
$return = '<pre>';
switch ($error->level) {
case LIBXML_ERR_WARNING:
$return .= "Warning $error->code: ";
break;
case LIBXML_ERR_ERROR:
$return .= "Error $error->code: ";
break;
case LIBXML_ERR_FATAL:
$return .= "Fatal Error $error->code: ";
break;
}
$return .= trim($error->message) .
"\n Line: $error->line";
return "$return\n\n--------------------------------------------\n\n</pre>";
}
