You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1061 lines
26 KiB

"""Various low level data validators."""
import calendar
from io import open
import fs.base
import fs.osfs
from collections.abc import Mapping
from fontTools.ufoLib.utils import numberTypes
# -------
# Generic
# -------
def isDictEnough(value):
"""
Some objects will likely come in that aren't
dicts but are dict-ish enough.
"""
if isinstance(value, Mapping):
return True
for attr in ("keys", "values", "items"):
if not hasattr(value, attr):
return False
return True
def genericTypeValidator(value, typ):
"""
Generic. (Added at version 2.)
"""
return isinstance(value, typ)
def genericIntListValidator(values, validValues):
"""
Generic. (Added at version 2.)
"""
if not isinstance(values, (list, tuple)):
return False
valuesSet = set(values)
validValuesSet = set(validValues)
if valuesSet - validValuesSet:
return False
for value in values:
if not isinstance(value, int):
return False
return True
def genericNonNegativeIntValidator(value):
"""
Generic. (Added at version 3.)
"""
if not isinstance(value, int):
return False
if value < 0:
return False
return True
def genericNonNegativeNumberValidator(value):
"""
Generic. (Added at version 3.)
"""
if not isinstance(value, numberTypes):
return False
if value < 0:
return False
return True
def genericDictValidator(value, prototype):
"""
Generic. (Added at version 3.)
"""
# not a dict
if not isinstance(value, Mapping):
return False
# missing required keys
for key, (typ, required) in prototype.items():
if not required:
continue
if key not in value:
return False
# unknown keys
for key in value.keys():
if key not in prototype:
return False
# incorrect types
for key, v in value.items():
prototypeType, required = prototype[key]
if v is None and not required:
continue
if not isinstance(v, prototypeType):
return False
return True
# --------------
# fontinfo.plist
# --------------
# Data Validators
def fontInfoStyleMapStyleNameValidator(value):
"""
Version 2+.
"""
options = ["regular", "italic", "bold", "bold italic"]
return value in options
def fontInfoOpenTypeGaspRangeRecordsValidator(value):
"""
Version 3+.
"""
if not isinstance(value, list):
return False
if len(value) == 0:
return True
validBehaviors = [0, 1, 2, 3]
dictPrototype = dict(rangeMaxPPEM=(int, True), rangeGaspBehavior=(list, True))
ppemOrder = []
for rangeRecord in value:
if not genericDictValidator(rangeRecord, dictPrototype):
return False
ppem = rangeRecord["rangeMaxPPEM"]
behavior = rangeRecord["rangeGaspBehavior"]
ppemValidity = genericNonNegativeIntValidator(ppem)
if not ppemValidity:
return False
behaviorValidity = genericIntListValidator(behavior, validBehaviors)
if not behaviorValidity:
return False
ppemOrder.append(ppem)
if ppemOrder != sorted(ppemOrder):
return False
return True
def fontInfoOpenTypeHeadCreatedValidator(value):
"""
Version 2+.
"""
# format: 0000/00/00 00:00:00
if not isinstance(value, str):
return False
# basic formatting
if not len(value) == 19:
return False
if value.count(" ") != 1:
return False
date, time = value.split(" ")
if date.count("/") != 2:
return False
if time.count(":") != 2:
return False
# date
year, month, day = date.split("/")
if len(year) != 4:
return False
if len(month) != 2:
return False
if len(day) != 2:
return False
try:
year = int(year)
month = int(month)
day = int(day)
except ValueError:
return False
if month < 1 or month > 12:
return False
monthMaxDay = calendar.monthrange(year, month)[1]
if day < 1 or day > monthMaxDay:
return False
# time
hour, minute, second = time.split(":")
if len(hour) != 2:
return False
if len(minute) != 2:
return False
if len(second) != 2:
return False
try:
hour = int(hour)
minute = int(minute)
second = int(second)
except ValueError:
return False
if hour < 0 or hour > 23:
return False
if minute < 0 or minute > 59:
return False
if second < 0 or second > 59:
return False
# fallback
return True
def fontInfoOpenTypeNameRecordsValidator(value):
"""
Version 3+.
"""
if not isinstance(value, list):
return False
dictPrototype = dict(nameID=(int, True), platformID=(int, True), encodingID=(int, True), languageID=(int, True), string=(str, True))
for nameRecord in value:
if not genericDictValidator(nameRecord, dictPrototype):
return False
return True
def fontInfoOpenTypeOS2WeightClassValidator(value):
"""
Version 2+.
"""
if not isinstance(value, int):
return False
if value < 0:
return False
return True
def fontInfoOpenTypeOS2WidthClassValidator(value):
"""
Version 2+.
"""
if not isinstance(value, int):
return False
if value < 1:
return False
if value > 9:
return False
return True
def fontInfoVersion2OpenTypeOS2PanoseValidator(values):
"""
Version 2.
"""
if not isinstance(values, (list, tuple)):
return False
if len(values) != 10:
return False
for value in values:
if not isinstance(value, int):
return False
# XXX further validation?
return True
def fontInfoVersion3OpenTypeOS2PanoseValidator(values):
"""
Version 3+.
"""
if not isinstance(values, (list, tuple)):
return False
if len(values) != 10:
return False
for value in values:
if not isinstance(value, int):
return False
if value < 0:
return False
# XXX further validation?
return True
def fontInfoOpenTypeOS2FamilyClassValidator(values):
"""
Version 2+.
"""
if not isinstance(values, (list, tuple)):
return False
if len(values) != 2:
return False
for value in values:
if not isinstance(value, int):
return False
classID, subclassID = values
if classID < 0 or classID > 14:
return False
if subclassID < 0 or subclassID > 15:
return False
return True
def fontInfoPostscriptBluesValidator(values):
"""
Version 2+.
"""
if not isinstance(values, (list, tuple)):
return False
if len(values) > 14:
return False
if len(values) % 2:
return False
for value in values:
if not isinstance(value, numberTypes):
return False
return True
def fontInfoPostscriptOtherBluesValidator(values):
"""
Version 2+.
"""
if not isinstance(values, (list, tuple)):
return False
if len(values) > 10:
return False
if len(values) % 2:
return False
for value in values:
if not isinstance(value, numberTypes):
return False
return True
def fontInfoPostscriptStemsValidator(values):
"""
Version 2+.
"""
if not isinstance(values, (list, tuple)):
return False
if len(values) > 12:
return False
for value in values:
if not isinstance(value, numberTypes):
return False
return True
def fontInfoPostscriptWindowsCharacterSetValidator(value):
"""
Version 2+.
"""
validValues = list(range(1, 21))
if value not in validValues:
return False
return True
def fontInfoWOFFMetadataUniqueIDValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(id=(str, True))
if not genericDictValidator(value, dictPrototype):
return False
return True
def fontInfoWOFFMetadataVendorValidator(value):
"""
Version 3+.
"""
dictPrototype = {"name" : (str, True), "url" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
return False
return True
def fontInfoWOFFMetadataCreditsValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(credits=(list, True))
if not genericDictValidator(value, dictPrototype):
return False
if not len(value["credits"]):
return False
dictPrototype = {"name" : (str, True), "url" : (str, False), "role" : (str, False), "dir" : (str, False), "class" : (str, False)}
for credit in value["credits"]:
if not genericDictValidator(credit, dictPrototype):
return False
if "dir" in credit and credit.get("dir") not in ("ltr", "rtl"):
return False
return True
def fontInfoWOFFMetadataDescriptionValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(url=(str, False), text=(list, True))
if not genericDictValidator(value, dictPrototype):
return False
for text in value["text"]:
if not fontInfoWOFFMetadataTextValue(text):
return False
return True
def fontInfoWOFFMetadataLicenseValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(url=(str, False), text=(list, False), id=(str, False))
if not genericDictValidator(value, dictPrototype):
return False
if "text" in value:
for text in value["text"]:
if not fontInfoWOFFMetadataTextValue(text):
return False
return True
def fontInfoWOFFMetadataTrademarkValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(text=(list, True))
if not genericDictValidator(value, dictPrototype):
return False
for text in value["text"]:
if not fontInfoWOFFMetadataTextValue(text):
return False
return True
def fontInfoWOFFMetadataCopyrightValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(text=(list, True))
if not genericDictValidator(value, dictPrototype):
return False
for text in value["text"]:
if not fontInfoWOFFMetadataTextValue(text):
return False
return True
def fontInfoWOFFMetadataLicenseeValidator(value):
"""
Version 3+.
"""
dictPrototype = {"name" : (str, True), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
return False
return True
def fontInfoWOFFMetadataTextValue(value):
"""
Version 3+.
"""
dictPrototype = {"text" : (str, True), "language" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
return False
return True
def fontInfoWOFFMetadataExtensionsValidator(value):
"""
Version 3+.
"""
if not isinstance(value, list):
return False
if not value:
return False
for extension in value:
if not fontInfoWOFFMetadataExtensionValidator(extension):
return False
return True
def fontInfoWOFFMetadataExtensionValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(names=(list, False), items=(list, True), id=(str, False))
if not genericDictValidator(value, dictPrototype):
return False
if "names" in value:
for name in value["names"]:
if not fontInfoWOFFMetadataExtensionNameValidator(name):
return False
for item in value["items"]:
if not fontInfoWOFFMetadataExtensionItemValidator(item):
return False
return True
def fontInfoWOFFMetadataExtensionItemValidator(value):
"""
Version 3+.
"""
dictPrototype = dict(id=(str, False), names=(list, True), values=(list, True))
if not genericDictValidator(value, dictPrototype):
return False
for name in value["names"]:
if not fontInfoWOFFMetadataExtensionNameValidator(name):
return False
for val in value["values"]:
if not fontInfoWOFFMetadataExtensionValueValidator(val):
return False
return True
def fontInfoWOFFMetadataExtensionNameValidator(value):
"""
Version 3+.
"""
dictPrototype = {"text" : (str, True), "language" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
return False
return True
def fontInfoWOFFMetadataExtensionValueValidator(value):
"""
Version 3+.
"""
dictPrototype = {"text" : (str, True), "language" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
return False
return True
# ----------
# Guidelines
# ----------
def guidelinesValidator(value, identifiers=None):
"""
Version 3+.
"""
if not isinstance(value, list):
return False
if identifiers is None:
identifiers = set()
for guide in value:
if not guidelineValidator(guide):
return False
identifier = guide.get("identifier")
if identifier is not None:
if identifier in identifiers:
return False
identifiers.add(identifier)
return True
_guidelineDictPrototype = dict(
x=((int, float), False), y=((int, float), False), angle=((int, float), False),
name=(str, False), color=(str, False), identifier=(str, False)
)
def guidelineValidator(value):
"""
Version 3+.
"""
if not genericDictValidator(value, _guidelineDictPrototype):
return False
x = value.get("x")
y = value.get("y")
angle = value.get("angle")
# x or y must be present
if x is None and y is None:
return False
# if x or y are None, angle must not be present
if x is None or y is None:
if angle is not None:
return False
# if x and y are defined, angle must be defined
if x is not None and y is not None and angle is None:
return False
# angle must be between 0 and 360
if angle is not None:
if angle < 0:
return False
if angle > 360:
return False
# identifier must be 1 or more characters
identifier = value.get("identifier")
if identifier is not None and not identifierValidator(identifier):
return False
# color must follow the proper format
color = value.get("color")
if color is not None and not colorValidator(color):
return False
return True
# -------
# Anchors
# -------
def anchorsValidator(value, identifiers=None):
"""
Version 3+.
"""
if not isinstance(value, list):
return False
if identifiers is None:
identifiers = set()
for anchor in value:
if not anchorValidator(anchor):
return False
identifier = anchor.get("identifier")
if identifier is not None:
if identifier in identifiers:
return False
identifiers.add(identifier)
return True
_anchorDictPrototype = dict(
x=((int, float), False), y=((int, float), False),
name=(str, False), color=(str, False),
identifier=(str, False)
)
def anchorValidator(value):
"""
Version 3+.
"""
if not genericDictValidator(value, _anchorDictPrototype):
return False
x = value.get("x")
y = value.get("y")
# x and y must be present
if x is None or y is None:
return False
# identifier must be 1 or more characters
identifier = value.get("identifier")
if identifier is not None and not identifierValidator(identifier):
return False
# color must follow the proper format
color = value.get("color")
if color is not None and not colorValidator(color):
return False
return True
# ----------
# Identifier
# ----------
def identifierValidator(value):
"""
Version 3+.
>>> identifierValidator("a")
True
>>> identifierValidator("")
False
>>> identifierValidator("a" * 101)
False
"""
validCharactersMin = 0x20
validCharactersMax = 0x7E
if not isinstance(value, str):
return False
if not value:
return False
if len(value) > 100:
return False
for c in value:
c = ord(c)
if c < validCharactersMin or c > validCharactersMax:
return False
return True
# -----
# Color
# -----
def colorValidator(value):
"""
Version 3+.
>>> colorValidator("0,0,0,0")
True
>>> colorValidator(".5,.5,.5,.5")
True
>>> colorValidator("0.5,0.5,0.5,0.5")
True
>>> colorValidator("1,1,1,1")
True
>>> colorValidator("2,0,0,0")
False
>>> colorValidator("0,2,0,0")
False
>>> colorValidator("0,0,2,0")
False
>>> colorValidator("0,0,0,2")
False
>>> colorValidator("1r,1,1,1")
False
>>> colorValidator("1,1g,1,1")
False
>>> colorValidator("1,1,1b,1")
False
>>> colorValidator("1,1,1,1a")
False
>>> colorValidator("1 1 1 1")
False
>>> colorValidator("1 1,1,1")
False
>>> colorValidator("1,1 1,1")
False
>>> colorValidator("1,1,1 1")
False
>>> colorValidator("1, 1, 1, 1")
True
"""
if not isinstance(value, str):
return False
parts = value.split(",")
if len(parts) != 4:
return False
for part in parts:
part = part.strip()
converted = False
try:
part = int(part)
converted = True
except ValueError:
pass
if not converted:
try:
part = float(part)
converted = True
except ValueError:
pass
if not converted:
return False
if part < 0:
return False
if part > 1:
return False
return True
# -----
# image
# -----
pngSignature = b"\x89PNG\r\n\x1a\n"
_imageDictPrototype = dict(
fileName=(str, True),
xScale=((int, float), False), xyScale=((int, float), False),
yxScale=((int, float), False), yScale=((int, float), False),
xOffset=((int, float), False), yOffset=((int, float), False),
color=(str, False)
)
def imageValidator(value):
"""
Version 3+.
"""
if not genericDictValidator(value, _imageDictPrototype):
return False
# fileName must be one or more characters
if not value["fileName"]:
return False
# color must follow the proper format
color = value.get("color")
if color is not None and not colorValidator(color):
return False
return True
def pngValidator(path=None, data=None, fileObj=None):
"""
Version 3+.
This checks the signature of the image data.
"""
assert path is not None or data is not None or fileObj is not None
if path is not None:
with open(path, "rb") as f:
signature = f.read(8)
elif data is not None:
signature = data[:8]
elif fileObj is not None:
pos = fileObj.tell()
signature = fileObj.read(8)
fileObj.seek(pos)
if signature != pngSignature:
return False, "Image does not begin with the PNG signature."
return True, None
# -------------------
# layercontents.plist
# -------------------
def layerContentsValidator(value, ufoPathOrFileSystem):
"""
Check the validity of layercontents.plist.
Version 3+.
"""
if isinstance(ufoPathOrFileSystem, fs.base.FS):
fileSystem = ufoPathOrFileSystem
else:
fileSystem = fs.osfs.OSFS(ufoPathOrFileSystem)
bogusFileMessage = "layercontents.plist in not in the correct format."
# file isn't in the right format
if not isinstance(value, list):
return False, bogusFileMessage
# work through each entry
usedLayerNames = set()
usedDirectories = set()
contents = {}
for entry in value:
# layer entry in the incorrect format
if not isinstance(entry, list):
return False, bogusFileMessage
if not len(entry) == 2:
return False, bogusFileMessage
for i in entry:
if not isinstance(i, str):
return False, bogusFileMessage
layerName, directoryName = entry
# check directory naming
if directoryName != "glyphs":
if not directoryName.startswith("glyphs."):
return False, "Invalid directory name (%s) in layercontents.plist." % directoryName
if len(layerName) == 0:
return False, "Empty layer name in layercontents.plist."
# directory doesn't exist
if not fileSystem.exists(directoryName):
return False, "A glyphset does not exist at %s." % directoryName
# default layer name
if layerName == "public.default" and directoryName != "glyphs":
return False, "The name public.default is being used by a layer that is not the default."
# check usage
if layerName in usedLayerNames:
return False, "The layer name %s is used by more than one layer." % layerName
usedLayerNames.add(layerName)
if directoryName in usedDirectories:
return False, "The directory %s is used by more than one layer." % directoryName
usedDirectories.add(directoryName)
# store
contents[layerName] = directoryName
# missing default layer
foundDefault = "glyphs" in contents.values()
if not foundDefault:
return False, "The required default glyph set is not in the UFO."
return True, None
# ------------
# groups.plist
# ------------
def groupsValidator(value):
"""
Check the validity of the groups.
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
>>> groups = {"A" : ["A", "A"], "A2" : ["A"]}
>>> groupsValidator(groups)
(True, None)
>>> groups = {"" : ["A"]}
>>> valid, msg = groupsValidator(groups)
>>> valid
False
>>> print(msg)
A group has an empty name.
>>> groups = {"public.awesome" : ["A"]}
>>> groupsValidator(groups)
(True, None)
>>> groups = {"public.kern1." : ["A"]}
>>> valid, msg = groupsValidator(groups)
>>> valid
False
>>> print(msg)
The group data contains a kerning group with an incomplete name.
>>> groups = {"public.kern2." : ["A"]}
>>> valid, msg = groupsValidator(groups)
>>> valid
False
>>> print(msg)
The group data contains a kerning group with an incomplete name.
>>> groups = {"public.kern1.A" : ["A"], "public.kern2.A" : ["A"]}
>>> groupsValidator(groups)
(True, None)
>>> groups = {"public.kern1.A1" : ["A"], "public.kern1.A2" : ["A"]}
>>> valid, msg = groupsValidator(groups)
>>> valid
False
>>> print(msg)
The glyph "A" occurs in too many kerning groups.
"""
bogusFormatMessage = "The group data is not in the correct format."
if not isDictEnough(value):
return False, bogusFormatMessage
firstSideMapping = {}
secondSideMapping = {}
for groupName, glyphList in value.items():
if not isinstance(groupName, (str)):
return False, bogusFormatMessage
if not isinstance(glyphList, (list, tuple)):
return False, bogusFormatMessage
if not groupName:
return False, "A group has an empty name."
if groupName.startswith("public."):
if not groupName.startswith("public.kern1.") and not groupName.startswith("public.kern2."):
# unknown public.* name. silently skip.
continue
else:
if len("public.kernN.") == len(groupName):
return False, "The group data contains a kerning group with an incomplete name."
if groupName.startswith("public.kern1."):
d = firstSideMapping
else:
d = secondSideMapping
for glyphName in glyphList:
if not isinstance(glyphName, str):
return False, "The group data %s contains an invalid member." % groupName
if glyphName in d:
return False, "The glyph \"%s\" occurs in too many kerning groups." % glyphName
d[glyphName] = groupName
return True, None
# -------------
# kerning.plist
# -------------
def kerningValidator(data):
"""
Check the validity of the kerning data structure.
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
>>> kerning = {"A" : {"B" : 100}}
>>> kerningValidator(kerning)
(True, None)
>>> kerning = {"A" : ["B"]}
>>> valid, msg = kerningValidator(kerning)
>>> valid
False
>>> print(msg)
The kerning data is not in the correct format.
>>> kerning = {"A" : {"B" : "100"}}
>>> valid, msg = kerningValidator(kerning)
>>> valid
False
>>> print(msg)
The kerning data is not in the correct format.
"""
bogusFormatMessage = "The kerning data is not in the correct format."
if not isinstance(data, Mapping):
return False, bogusFormatMessage
for first, secondDict in data.items():
if not isinstance(first, str):
return False, bogusFormatMessage
elif not isinstance(secondDict, Mapping):
return False, bogusFormatMessage
for second, value in secondDict.items():
if not isinstance(second, str):
return False, bogusFormatMessage
elif not isinstance(value, numberTypes):
return False, bogusFormatMessage
return True, None
# -------------
# lib.plist/lib
# -------------
_bogusLibFormatMessage = "The lib data is not in the correct format: %s"
def fontLibValidator(value):
"""
Check the validity of the lib.
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
>>> lib = {"foo" : "bar"}
>>> fontLibValidator(lib)
(True, None)
>>> lib = {"public.awesome" : "hello"}
>>> fontLibValidator(lib)
(True, None)
>>> lib = {"public.glyphOrder" : ["A", "C", "B"]}
>>> fontLibValidator(lib)
(True, None)
>>> lib = "hello"
>>> valid, msg = fontLibValidator(lib)
>>> valid
False
>>> print(msg) # doctest: +ELLIPSIS
The lib data is not in the correct format: expected a dictionary, ...
>>> lib = {1: "hello"}
>>> valid, msg = fontLibValidator(lib)
>>> valid
False
>>> print(msg)
The lib key is not properly formatted: expected str, found int: 1
>>> lib = {"public.glyphOrder" : "hello"}
>>> valid, msg = fontLibValidator(lib)
>>> valid
False
>>> print(msg) # doctest: +ELLIPSIS
public.glyphOrder is not properly formatted: expected list or tuple,...
>>> lib = {"public.glyphOrder" : ["A", 1, "B"]}
>>> valid, msg = fontLibValidator(lib)
>>> valid
False
>>> print(msg) # doctest: +ELLIPSIS
public.glyphOrder is not properly formatted: expected str,...
"""
if not isDictEnough(value):
reason = "expected a dictionary, found %s" % type(value).__name__
return False, _bogusLibFormatMessage % reason
for key, value in value.items():
if not isinstance(key, str):
return False, (
"The lib key is not properly formatted: expected str, found %s: %r" %
(type(key).__name__, key))
# public.glyphOrder
if key == "public.glyphOrder":
bogusGlyphOrderMessage = "public.glyphOrder is not properly formatted: %s"
if not isinstance(value, (list, tuple)):
reason = "expected list or tuple, found %s" % type(value).__name__
return False, bogusGlyphOrderMessage % reason
for glyphName in value:
if not isinstance(glyphName, str):
reason = "expected str, found %s" % type(glyphName).__name__
return False, bogusGlyphOrderMessage % reason
return True, None
# --------
# GLIF lib
# --------
def glyphLibValidator(value):
"""
Check the validity of the lib.
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
>>> lib = {"foo" : "bar"}
>>> glyphLibValidator(lib)
(True, None)
>>> lib = {"public.awesome" : "hello"}
>>> glyphLibValidator(lib)
(True, None)
>>> lib = {"public.markColor" : "1,0,0,0.5"}
>>> glyphLibValidator(lib)
(True, None)
>>> lib = {"public.markColor" : 1}
>>> valid, msg = glyphLibValidator(lib)
>>> valid
False
>>> print(msg)
public.markColor is not properly formatted.
"""
if not isDictEnough(value):
reason = "expected a dictionary, found %s" % type(value).__name__
return False, _bogusLibFormatMessage % reason
for key, value in value.items():
if not isinstance(key, str):
reason = "key (%s) should be a string" % key
return False, _bogusLibFormatMessage % reason
# public.markColor
if key == "public.markColor":
if not colorValidator(value):
return False, "public.markColor is not properly formatted."
return True, None
if __name__ == "__main__":
import doctest
doctest.testmod()