CloverBootloader/rEFIt_UEFI/Settings/ConfigPlist/README.md

11 KiB

How to use the xmlLite tools to read and validate plist

Creation of a dict

Example 1 :

class MyDictClass : public XmlDict
{
    using super = XmlDict;
  public:
    XmlBool aBool;
    XmlInt32 anInt32;

    XmlDictField m_fields[2] = {
        {"KeyNameForBool", aBool},
        {"KeyNameForInt32", anInt32},
    };
    virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
} MyDict;
  1. your class must heritate from XmlDict

  2. define an array to create a link between the xml key in the dict and the field.

  3. override method getFields() to return that array.

This can read this file

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>KeyNameForBool</key>
    <true/>
    <key>KeyNameForInt32</key>
    <integer>13864</integer>
</dict>

NOTE : the top level dict can heritate from ConfigPlistAbstractClass instead. That'll give you parse() methods to parse from a buffer

Values

A dict is composed of key value pair. In the previous we used XmlBool and XmlInt32. In XmlLiteSimpleType.h there is few others :

  • XmlBool

  • XmlStrictBool

  • XmlString8

  • XmlStringW

  • XmlString8AllowEmpty

  • XmlString8Trimed

  • XmlData

  • XmlUInt{8, 16 32} and XmlInt {8, 16 32}

  • Of course, you can declare an XmlDict as a value in a containing dict. Example 2 :

class MyInsideDictClass : public XmlDict
{
    using super = XmlDict;
  public:
    XmlBool aBool;
    XmlInt32 anInt32;

    XmlDictField m_fields[2] = {
        {"KeyNameForBool", aBool},
        {"KeyNameForInt32", anInt32},
    };
    virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
};

class MyTopLevelDictClass : public ConfigPlistAbstractClass
{
    using super = XmlDict;
  public:
    MyInsideDictClass inside1;

    XmlDictField m_fields[1] = {
        {"KeyNameForInsideDict", inside1},
    };
    virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
} MyDict;

This can read this file

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>KeyNameForInsideDict</key>
    <dict>
      <key>KeyNameForBool</key>
      <true/>
      <key>KeyNameForInt32</key>
      <integer>13864</integer>
  </dict>
</dict>
  • Arrays

Validation

Validation of a single value

To validate and reject bad input, override the method validate. Imagine we have an integer value that is a count between -2 and 18. Example 3 :

class CountClass : public XmlInt64
{
  using super = XmlInt64;
  virtual XBool validate(XmlLiteParser* xmlLiteParser, const XString8& xmlPath, const XmlParserPosition& keyPos, XBool generateErrors) override {
    bool b = super::validate(xmlLiteParser, xmlPath, keyPos, generateErrors);
    if ( value() < -2 ) {
        b = xmlLiteParser->addWarning(generateErrors, S8Printf("Count cannot be negative. It must a number between -2 and 18 inclusive at '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
    }
    if ( value() > 18 ) {
        b = xmlLiteParser->addWarning(generateErrors, S8Printf("Count cannot > 18. It must a number between -2 and 18 inclusive at '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
    }
    return b;
  }
} Count = CountClass();

Returning false will put back the value to an undefined state.

In your dict, declare a member CountClass Count. If the value is out of range, Count will be undefined.

Validation of interdependent values

To check for consistency of values in a dict, you override the same validate method for your dict.

Imagine you have a dict containing a type (1 or 2) and a subType, but type 1 cannot have subtype. Type 2 may have subtype, and if there is, it must be 11 or 12. Example 4 :

class MyDictClass : public XmlDict
{
    using super = XmlDict;
  public:
    MyXmlType type;       // this is a subclass of XmlUInt8 that check that type is 1 or 2
    MyXmlSubType subType; // this is a subclass of XmlUInt8 that check that subtype is 11 or 12
    XmlString8 name;      // as many other field that there is in this dict

    XmlDictField m_fields[2] = {
        {"Type", type},
        {"SubType", subType},
    };
    virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };

    virtual XBool validate(XmlLiteParser* xmlLiteParser, const XString8& xmlPath, const XmlParserPosition& keyPos, XBool generateErrors) override {
      bool b = super::validate(xmlLiteParser, xmlPath, keyPos, generateErrors);
      if ( !type.isDefined() ) {
          b = xmlLiteParser->addWarning(generateErrors, S8Printf("Type must befined at '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
      }else
      if ( type.value() == 1 ) {
        if ( subType.isDefined() ) {
            b = xmlLiteParser->addWarning(generateErrors, S8Printf("Type 1 cannot have a subtype at '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
        }
      }else if ( type.value() == 2 ) {
        // nothing to do because subtype is optional, and if it exists, weknow that the value is correct because of th validation in MyXmlSubType
      }else{
        panic("There is a bug in MyXmlType::validate() !");
      }
      return b;
    }
};

Returning false will put back the dict to an undefined state. Which means, in that case, the whole dict to be undefined as it wasn't at all in the XML file.

NOTE : it's possible to do the single field validation at dict level. The previous example could written as example 5 :

class MyDictClass : public XmlDict
{
    using super = XmlDict;
  public:
    XmlUInt8 type; // no validation except that the value is an unsigned 8 bits int 
    XmlUInt8 subType; // no validation except that the value is an unsigned 8 bits int 
    XmlString8 name;      // as many other field that there is in this dict

    XmlDictField m_fields[2] = {
        {"Type", type},
        {"SubType", subType},
    };
    virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };

    virtual XBool validate(XmlLiteParser* xmlLiteParser, const XString8& xmlPath, const XmlParserPosition& keyPos, XBool generateErrors) override {
      bool b = super::validate(xmlLiteParser, xmlPath, keyPos, generateErrors);
      if ( !type.isDefined() ) {
          b = xmlLiteParser->addWarning(generateErrors, S8Printf("Type must befined at '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
      }else
      if ( type.value() == 1 ) {
        if ( subType.isDefined() ) {
            b = xmlLiteParser->addWarning(generateErrors, S8Printf("Type 1 cannot have a subtype in dict '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
        }
      }else if ( type.value() == 2 ) {
        if ( subType.isDefined() ) {
            if ( subType.value() != 11  &&  subType.value() != 12 ) {
              b = xmlLiteParser->addWarning(generateErrors, S8Printf("SubType must be 11 or 12 at '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
            }
        }else{
          // subtype is optional, so it's ok.
        }
      }else{
        xmlLiteParser->addWarning(generateErrors, S8Printf("Type must be 1 or 2 at '%s:%d'", xmlPath.c_str(), keyPos.getLine()));
        // Let's think that we want to ignore this value but we still want to keep the dict as the other field still has meaning.
        // That's why we don't set b to false
        type.reset(); // We only reset this field. We don't return false because that'll undefine the whole dict
        subType.reset(); // SubType means nothing without a Type.
      }
      return b;
    }
};

Arrays

To create an array, declare an XmlArray<[any class that inherite from XmlAbstractType]> member. Example 6 :

class MyDictClass : public XmlDict
{
    using super = XmlDict;
  public:
    XmlArray<XmlBool> aBoolArray;

    XmlDictField m_fields[2] = {
        {"KeyNameForBoolArray", aBoolArray},
    };
    virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
};

This can read this file :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>KeyNameForBoolArray</key>
    <array>
      <true/>
      <false/>
      <true/>
      ... {as many as ther is}
  </dict>
</dict>

Of course, you can create an XmlArray of a dict you created.

Repeating dict

Until now, examples shown a dict that has a predefined set of keys. But you can have a dict that contains repeating key/value pair. The key could be any string.

For that, you have a template class XmlRepeatingDict. To create a dict that can contains a sequence of key and int32. Example 7 :

class MyDictClass : public ConfigPlistAbstractClass
{
    using super = XmlDict;
  public:
    XmlRepeatingDict<XmlAddKey<XmlKey, XmlInt32>> keyIntPairs;

    XmlDictField m_fields[1] = {
        {"KeyNameForKeyIntPairs", keyIntPairs},
    };
    virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
} MyDict;

Because a dict has to be a sequence of key and value, please note the use of the class XmlAddKey<XmlKey, XmlInt32>> that "adds" a key to a XmlInt32. You can replace XmlInt32 by any type the inherite from XmlAbstractType. That includes your own dicts and arrays.

The example above will parse a file like :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>KeyNameForKeyIntPairs</key>
    <dict>
        <key>a key</key>
        <integer>1</integer>
        <key>another key</key>
        <integer>2</integer>
        <key>third key</key>
        <integer>3</integer>
    </dict>
</dict>

After the dict is parsed, you can access values this way :

XObjArray<XmlAddKey<XmlKey, XmlInt32>> array = MyDict.keyIntPairs.valueArray();
XString8 keyOne = array[1].key(); // == "another key"
int32_t valueOne = array[1].value(); // == 2

Union

Sometimes, a value can be, for example, a XBool or a string. In that case it's easy to declare. Just subclass XmlUnion :

class XmlBoolOrString : public XmlUnion
{
  using super = XmlUnion;
public:
  XmlBool xmlBool = XmlBool();
  XmlString8 xmlString8 = XmlString8();
  virtual const char* getDescription() override { return "XBool or string"; };
  XmlUnionField m_fields[2] = { xmlBool, xmlString8 };
  virtual void getFields(XmlUnionField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
};

In this example, the value will be tried to be parsed as a XBool. If it doesn't work, the next possibility is tried. Here it's xmlString8. You can create union with any kind of field you create, including dict and arrays.