# Source code for fontTools.ufoLib.validators

    """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()