diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index d0ac51089..723c09800 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -43,6 +43,8 @@ __all__ = [ class GlifLibError(Exception): pass +class MissingPlistError(GlifLibError): pass + # --------- # Constants # --------- @@ -128,17 +130,17 @@ class GlyphSet(object): Rebuild the contents dict by loading contents.plist. """ contentsPath = os.path.join(self.dirName, "contents.plist") - if not os.path.exists(contentsPath): + try: + contents = self._readPlist(contentsPath) + except MissingPlistError: # missing, consider the glyphset empty. contents = {} - else: - contents = self._readPlist(contentsPath) # validate the contents invalidFormat = False if not isinstance(contents, dict): invalidFormat = True else: - for name, fileName in list(contents.items()): + for name, fileName in contents.items(): if not isinstance(name, basestring): invalidFormat = True if not isinstance(fileName, basestring): @@ -179,14 +181,15 @@ class GlyphSet(object): def readLayerInfo(self, info): path = os.path.join(self.dirName, LAYERINFO_FILENAME) - if not os.path.exists(path): + try: + infoDict = self._readPlist(path) + except MissingPlistError: return - infoDict = self._readPlist(path) if not isinstance(infoDict, dict): raise GlifLibError("layerinfo.plist is not properly formatted.") infoDict = validateLayerInfoVersion3Data(infoDict) # populate the object - for attr, value in list(infoDict.items()): + for attr, value in infoDict.items(): try: setattr(info, attr, value) except AttributeError: @@ -197,7 +200,7 @@ class GlyphSet(object): raise GlifLibError("layerinfo.plist is not allowed in UFO %d." % self.ufoFormatVersion) # gather data infoData = {} - for attr in list(layerInfoVersion3ValueData.keys()): + for attr in layerInfoVersion3ValueData.keys(): if hasattr(info, attr): try: value = getattr(info, attr) @@ -242,10 +245,13 @@ class GlyphSet(object): needRead = True if needRead: fileName = self.contents[glyphName] - if not os.path.exists(path): - raise KeyError(glyphName) - with open(path, "rb") as f: - text = f.read() + try: + with open(path, "rb") as f: + text = f.read() + except IOError as e: + if e.errno == 2: # aka. FileNotFoundError + raise KeyError(glyphName) + raise self._glifCache[glyphName] = (text, os.path.getmtime(path)) return self._glifCache[glyphName][0] @@ -413,7 +419,7 @@ class GlyphSet(object): """ components = {} if glyphNames is None: - glyphNames = list(self.contents.keys()) + glyphNames = self.contents.keys() for glyphName in glyphNames: text = self.getGLIF(glyphName) components[glyphName] = _fetchComponentBases(text) @@ -428,7 +434,7 @@ class GlyphSet(object): """ images = {} if glyphNames is None: - glyphNames = list(self.contents.keys()) + glyphNames = self.contents.keys() for glyphName in glyphNames: text = self.getGLIF(glyphName) images[glyphName] = _fetchImageFileName(text) @@ -441,7 +447,9 @@ class GlyphSet(object): with open(path, "rb") as f: data = readPlist(f) return data - except: + except Exception as e: + if isinstance(e, IOError) and e.errno == 2: + raise MissingPlistError(path) raise GlifLibError("The file %s could not be read." % path) @@ -454,9 +462,9 @@ def glyphNameToFileName(glyphName, glyphSet): Wrapper around the userNameToFileName function in filenames.py """ if glyphSet: - existing = [name.lower() for name in list(glyphSet.contents.values())] + existing = frozenset(name.lower() for name in glyphSet.contents.values()) else: - existing = [] + existing = frozenset() if not isinstance(glyphName, unicode): try: new = unicode(glyphName) @@ -595,13 +603,13 @@ def _writeAdvance(glyphObject, writer): if not isinstance(width, (int, float)): raise GlifLibError("width attribute must be int or float") if width == 0: - width = None + width = None height = getattr(glyphObject, "height", None) if height is not None: if not isinstance(height, (int, float)): raise GlifLibError("height attribute must be int or float") if height == 0: - height = None + height = None if width is not None and height is not None: writer.simpletag("advance", width=repr(width), height=repr(height)) writer.newline() @@ -789,16 +797,13 @@ def validateLayerInfoVersion3Data(infoData): a set range of possible values for an attribute, that the value is in the accepted range. """ - validInfoData = {} - for attr, value in list(infoData.items()): + for attr, value in infoData.items(): if attr not in layerInfoVersion3ValueData: raise GlifLibError("Unknown attribute %s." % attr) isValidValue = validateLayerInfoVersion3ValueForAttribute(attr, value) if not isValidValue: raise GlifLibError("Invalid value for attribute %s (%s)." % (attr, repr(value))) - else: - validInfoData[attr] = value - return validInfoData + return infoData # ----------------- # GLIF Tree Support @@ -1067,7 +1072,7 @@ def _buildOutlineContourFormat1(pen, contour): pen.endPath() def _buildOutlinePointsFormat1(pen, contour): - for index, element in enumerate(contour): + for element in contour: x = element.attrib["x"] y = element.attrib["y"] segmentType = element.attrib["segmentType"] @@ -1078,8 +1083,9 @@ def _buildOutlinePointsFormat1(pen, contour): def _buildOutlineComponentFormat1(pen, component): if len(component): raise GlifLibError("Unknown child elements of component element.") - if set(component.attrib.keys()) - componentAttributesFormat1: - raise GlifLibError("Unknown attributes in component element.") + for attr in component.attrib.keys(): + if attr not in componentAttributesFormat1: + raise GlifLibError("Unknown attribute in component element: %s" % attr) baseGlyphName = component.get("base") if baseGlyphName is None: raise GlifLibError("The base attribute is not defined in the component.") @@ -1105,8 +1111,9 @@ def buildOutlineFormat2(glyphObject, pen, outline, identifiers): raise GlifLibError("Unknown element in outline element: %s" % element.tag) def _buildOutlineContourFormat2(pen, contour, identifiers): - if set(contour.attrib.keys()) - contourAttributesFormat2: - raise GlifLibError("Unknown attributes in contour element.") + for attr in contour.attrib.keys(): + if attr not in contourAttributesFormat2: + raise GlifLibError("Unknown attribute in contour element: %s" % attr) identifier = contour.get("identifier") if identifier is not None: if identifier in identifiers: @@ -1125,7 +1132,7 @@ def _buildOutlineContourFormat2(pen, contour, identifiers): pen.endPath() def _buildOutlinePointsFormat2(pen, contour, identifiers): - for index, element in enumerate(contour): + for element in contour: x = element.attrib["x"] y = element.attrib["y"] segmentType = element.attrib["segmentType"] @@ -1147,8 +1154,9 @@ def _buildOutlinePointsFormat2(pen, contour, identifiers): def _buildOutlineComponentFormat2(pen, component, identifiers): if len(component): raise GlifLibError("Unknown child elements of component element.") - if set(component.attrib.keys()) - componentAttributesFormat2: - raise GlifLibError("Unknown attributes in component element.") + for attr in component.attrib.keys(): + if attr not in componentAttributesFormat2: + raise GlifLibError("Unknown attribute in component element: %s" % attr) baseGlyphName = component.get("base") if baseGlyphName is None: raise GlifLibError("The base attribute is not defined in the component.") @@ -1187,21 +1195,18 @@ def _validateAndMassagePointStructures(contour, pointAttributes, openContourOffC if element.tag != "point": raise GlifLibError("Unknown child element (%s) of contour element." % element.tag) # unknown attributes - unknownAttributes = [attr for attr in list(element.attrib.keys()) if attr not in pointAttributes] - if unknownAttributes: - raise GlifLibError("Unknown attributes in point element.") + for attr in element.attrib.keys(): + if attr not in pointAttributes: + raise GlifLibError("Unknown attribute in point element: %s" % attr) # search for unknown children if len(element): raise GlifLibError("Unknown child elements in point element.") # x and y are required - x = element.get("x") - y = element.get("y") - if x is None: - raise GlifLibError("Required x attribute is missing in point element.") - if y is None: - raise GlifLibError("Required y attribute is missing in point element.") - element.attrib["x"] = _number(x) - element.attrib["y"] = _number(y) + for attr in ("x", "y"): + value = element.get(attr) + if value is None: + raise GlifLibError("Required %s attribute is missing in point element." % attr) + element.attrib[attr] = _number(value) # segment type pointType = element.attrib.pop("type", "offcurve") if pointType not in pointTypeOptions: @@ -1243,8 +1248,7 @@ def _validateAndMassagePointStructures(contour, pointAttributes, openContourOffC # we only care about how many offCurves there are before an onCurve # filter out the trailing offCurves offCurvesCount = len(contour) - 1 - lastOnCurvePoint - stripedContour = contour[:-offCurvesCount] if offCurvesCount else contour - for element in stripedContour: + for element in contour: segmentType = element.attrib["segmentType"] if segmentType is None: offCurvesCount += 1 diff --git a/Lib/ufoLib/validators.py b/Lib/ufoLib/validators.py index 4324bfed5..af3f99896 100644 --- a/Lib/ufoLib/validators.py +++ b/Lib/ufoLib/validators.py @@ -28,8 +28,7 @@ def isDictEnough(value): """ if isinstance(value, Mapping): return True - attrs = ("keys", "values", "items") - for attr in attrs: + for attr in ("keys", "values", "items"): if not hasattr(value, attr): return False return True @@ -48,7 +47,7 @@ def genericIntListValidator(values, validValues): return False valuesSet = set(values) validValuesSet = set(validValues) - if len(valuesSet - validValuesSet) > 0: + if valuesSet - validValuesSet: return False for value in values: if not isinstance(value, int): @@ -83,17 +82,17 @@ def genericDictValidator(value, prototype): if not isinstance(value, Mapping): return False # missing required keys - for key, (typ, required) in list(prototype.items()): + for key, (typ, required) in prototype.items(): if not required: continue if key not in value: return False # unknown keys - for key in list(value.keys()): + for key in value.keys(): if key not in prototype: return False # incorrect types - for key, v in list(value.items()): + for key, v in value.items(): prototypeType, required = prototype[key] if v is None and not required: continue @@ -530,15 +529,16 @@ def guidelinesValidator(value, identifiers=None): identifiers.add(identifier) return True +_guidelineDictPrototype = dict( + x=((int, float), False), y=((int, float), False), angle=((int, float), False), + name=(basestring, False), color=(basestring, False), identifier=(basestring, False) +) + def guidelineValidator(value): """ Version 3+. """ - dictPrototype = dict( - x=((int, float), False), y=((int, float), False), angle=((int, float), False), - name=(basestring, False), color=(basestring, False), identifier=(basestring, False) - ) - if not genericDictValidator(value, dictPrototype): + if not genericDictValidator(value, _guidelineDictPrototype): return False x = value.get("x") y = value.get("y") @@ -591,16 +591,17 @@ def anchorsValidator(value, identifiers=None): identifiers.add(identifier) return True +_anchorDictPrototype = dict( + x=((int, float), False), y=((int, float), False), + name=(basestring, False), color=(basestring, False), + identifier=(basestring, False) +) + def anchorValidator(value): """ Version 3+. """ - dictPrototype = dict( - x=((int, float), False), y=((int, float), False), - name=(basestring, False), color=(basestring, False), - identifier=(basestring, False) - ) - if not genericDictValidator(value, dictPrototype): + if not genericDictValidator(value, _anchorDictPrototype): return False x = value.get("x") y = value.get("y") @@ -726,18 +727,19 @@ def colorValidator(value): pngSignature = b"\x89PNG\r\n\x1a\n" +_imageDictPrototype = dict( + fileName=(basestring, 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=(basestring, False) +) + def imageValidator(value): """ Version 3+. """ - dictPrototype = dict( - fileName=(basestring, 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=(basestring, False) - ) - if not genericDictValidator(value, dictPrototype): + if not genericDictValidator(value, _imageDictPrototype): return False # fileName must be one or more characters if not value["fileName"]: @@ -818,7 +820,7 @@ def layerContentsValidator(value, ufoPath): # store contents[layerName] = directoryName # missing default layer - foundDefault = "glyphs" in list(contents.values()) + foundDefault = "glyphs" in contents.values() if not foundDefault: return False, "The required default glyph set is not in the UFO." return True, None @@ -864,7 +866,7 @@ def groupsValidator(value): return False, bogusFormatMessage firstSideMapping = {} secondSideMapping = {} - for groupName, glyphList in list(value.items()): + for groupName, glyphList in value.items(): if not isinstance(groupName, (basestring)): return False, bogusFormatMessage if not isinstance(glyphList, (list, tuple)): @@ -914,12 +916,12 @@ def kerningValidator(data): bogusFormatMessage = "The kerning data is not in the correct format." if not isinstance(data, Mapping): return False, bogusFormatMessage - for first, secondDict in list(data.items()): + for first, secondDict in data.items(): if not isinstance(first, basestring): return False, bogusFormatMessage elif not isinstance(secondDict, Mapping): return False, bogusFormatMessage - for second, value in list(secondDict.items()): + for second, value in secondDict.items(): if not isinstance(second, basestring): return False, bogusFormatMessage elif not isinstance(value, (int, float)): @@ -930,6 +932,8 @@ def kerningValidator(data): # lib.plist/lib # ------------- +_bogusLibFormatMessage = "The lib data is not in the correct format: %s" + def fontLibValidator(value): """ Check the validity of the lib. @@ -967,11 +971,10 @@ def fontLibValidator(value): >>> fontLibValidator(lib) (False, 'public.glyphOrder is not properly formatted: expected basestring, found int') """ - bogusFormatMessage = "The lib data is not in the correct format: %s" if not isDictEnough(value): reason = "expected a dictionary, found %s" % type(value).__name__ - return False, bogusFormatMessage % reason - for key, value in list(value.items()): + return False, _bogusLibFormatMessage % reason + for key, value in value.items(): if not isinstance(key, basestring): return False, ( "The lib key is not properly formatted: expected basestring, found %s: %r" % @@ -1013,12 +1016,13 @@ def glyphLibValidator(value): >>> glyphLibValidator(lib) (False, 'public.markColor is not properly formatted.') """ - bogusFormatMessage = "The lib data is not in the correct format." if not isDictEnough(value): - return False, bogusFormatMessage - for key, value in list(value.items()): + reason = "expected a dictionary, found %s" % type(value).__name__ + return False, _bogusLibFormatMessage % reason + for key, value in value.items(): if not isinstance(key, basestring): - return False, bogusFormatMessage + reason = "key (%s) should be a string" % key + return False, _bogusLibFormatMessage % reason # public.markColor if key == "public.markColor": if not colorValidator(value):