#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of cpe package.
This module allows to store the value of the string components
of a CPE name and compare it with others.
Copyright (C) 2013 Alejandro Galindo García, Roberto Abdelkader Martínez Pérez
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
For any problems using the cpe package, or general questions and
feedback about it, please contact:
- Alejandro Galindo García: galindo.garcia.alejandro@gmail.com
- Roberto Abdelkader Martínez Pérez: robertomartinezp@gmail.com
"""
from cpecomp import CPEComponent
import re
[docs]class CPEComponentSimple(CPEComponent):
"""
Represents a generic string component of CPE name,
compatible with the components of all versions of CPE specification.
"""
###############
# CONSTANTS #
###############
#: Pattern to check if a character is a alphanumeric or underscore
_ALPHANUM_PATTERN = "\w"
#: Pattern to check the value of language component of CPE name
_LANGTAG_PATTERN = "^([a-z]{2,3}(-([a-z]{2}|[\d]{3}))?)$"
#: Pattern to check the value of part component of CPE name
_PART_PATTERN = "^(h|o|a)$"
###############
# VARIABLES #
###############
#: Characters to convert to percent-encoded characters when converts
#: WFN to URI
spechar_to_pce = {
'!': "%21",
'"': "%22",
'#': "%23",
'$': "%24",
'%': "%25",
'&': "%26",
'\'': "%27",
'(': "%28",
')': "%29",
'*': "%2a",
'+': "%2b",
',': "%2c",
'/': "%2f",
':': "%3a",
';': "%3b",
'<': "%3c",
'=': "%3d",
'>': "%3e",
'?': "%3f",
'@': "%40",
'[': "%5b",
'\\': "%5c",
']': "%5d",
'^': "%5e",
'`': "%60",
'{': "%7b",
'|': "%7c",
'}': "%7d",
'~': "%7e"}
###################
# CLASS METHODS #
###################
@classmethod
def _is_alphanum(cls, c):
"""
Returns True if c is an uppercase letter, a lowercase letter,
a digit or an underscore, otherwise False.
:param string c: Character to check
:returns: True if char is alphanumeric or an underscore,
False otherwise
:rtype: boolean
TEST: a wrong character
>>> c = "#"
>>> CPEComponentSimple._is_alphanum(c)
False
"""
alphanum_rxc = re.compile(CPEComponentSimple._ALPHANUM_PATTERN)
return (alphanum_rxc.match(c) is not None)
@classmethod
def _pct_encode_uri(cls, c):
"""
Return the appropriate percent-encoding of character c (URI string).
Certain characters are returned without encoding.
:param string c: Character to check
:returns: Encoded character as URI
:rtype: string
TEST:
>>> c = '.'
>>> CPEComponentSimple._pct_encode_uri(c)
'.'
TEST:
>>> c = '@'
>>> CPEComponentSimple._pct_encode_uri(c)
'%40'
"""
CPEComponentSimple.spechar_to_pce['-'] = c # bound without encoding
CPEComponentSimple.spechar_to_pce['.'] = c # bound without encoding
return CPEComponentSimple.spechar_to_pce[c]
####################
# OBJECT METHODS #
####################
[docs] def __init__(self, comp_str, comp_att):
"""
Store the value of component.
:param string comp_str: value of component value
:param string comp_att: attribute associated with component value
:returns: None
:exception: ValueError - incorrect value of component
"""
super(CPEComponentSimple, self).__init__(comp_str)
self._standard_value = self._standard_value
self.set_value(comp_str, comp_att)
[docs] def __str__(self):
"""
Returns a human-readable representation of CPE component.
:returns: Representation of CPE component as string
:rtype: string
"""
return self.get_value()
def _is_valid_edition(self):
"""
Return True if the value of component in attribute "edition" is valid,
and otherwise False.
:returns: True if value is valid, False otherwise
:rtype: boolean
"""
return self._is_valid_value() is not None
def _is_valid_language(self):
"""
Return True if the value of component in attribute "language" is valid,
and otherwise False.
:returns: True if value is valid, False otherwise
:rtype: boolean
"""
comp_str = self._encoded_value.lower()
lang_rxc = re.compile(CPEComponentSimple._LANGTAG_PATTERN)
return lang_rxc.match(comp_str) is not None
def _is_valid_part(self):
"""
Return True if the value of component in attribute "part" is valid,
and otherwise False.
:returns: True if value of component is valid, False otherwise
:rtype: boolean
"""
comp_str = self._encoded_value.lower()
part_rxc = re.compile(CPEComponentSimple._PART_PATTERN)
return part_rxc.match(comp_str) is not None
def _is_valid_value(self):
"""
Return True if the value of component in generic attribute is valid,
and otherwise False.
:returns: True if value is valid, False otherwise
:rtype: boolean
:exception: NotImplementedError - class method not implemented
"""
errmsg = "Class method not implemented. Use the method of some child class"
raise NotImplementedError(errmsg)
def _parse(self, comp_att):
"""
Check if the value of component is correct in the attribute "comp_att".
:param string comp_att: attribute associated with value of component
:returns: None
:exception: ValueError - incorrect value of component
"""
errmsg = "Invalid attribute '{0}'".format(comp_att)
if not CPEComponent.is_valid_attribute(comp_att):
raise ValueError(errmsg)
comp_str = self._encoded_value
errmsg = "Invalid value of attribute '{0}': {1}".format(
comp_att, comp_str)
# Check part (system type) value
if comp_att == CPEComponentSimple.ATT_PART:
if not self._is_valid_part():
raise ValueError(errmsg)
# Check language value
elif comp_att == CPEComponentSimple.ATT_LANGUAGE:
if not self._is_valid_language():
raise ValueError(errmsg)
# Check edition value
elif comp_att == CPEComponentSimple.ATT_EDITION:
if not self._is_valid_edition():
raise ValueError(errmsg)
# Check other type of component value
elif not self._is_valid_value():
raise ValueError(errmsg)
[docs] def as_fs(self):
"""
Returns the value of component encoded as formatted string.
Inspect each character in value of component.
Certain nonalpha characters pass thru without escaping
into the result, but most retain escaping.
:returns: Formatted string associated with component
:rtype: string
"""
s = self._standard_value
result = []
idx = 0
while (idx < len(s)):
c = s[idx] # get the idx'th character of s
if c != "\\":
# unquoted characters pass thru unharmed
result.append(c)
else:
# Escaped characters are examined
nextchr = s[idx + 1]
if (nextchr == ".") or (nextchr == "-") or (nextchr == "_"):
# the period, hyphen and underscore pass unharmed
result.append(nextchr)
idx += 1
else:
# all others retain escaping
result.append("\\")
result.append(nextchr)
idx += 2
continue
idx += 1
return "".join(result)
[docs] def as_uri_2_3(self):
"""
Returns the value of component encoded as URI string.
Scans an input string s and applies the following transformations:
- Pass alphanumeric characters thru untouched
- Percent-encode quoted non-alphanumerics as needed
- Unquoted special characters are mapped to their special forms.
:returns: URI string associated with component
:rtype: string
"""
s = self._standard_value
result = []
idx = 0
while (idx < len(s)):
thischar = s[idx] # get the idx'th character of s
# alphanumerics (incl. underscore) pass untouched
if (CPEComponentSimple._is_alphanum(thischar)):
result.append(thischar)
idx += 1
continue
# escape character
if (thischar == "\\"):
idx += 1
nxtchar = s[idx]
result.append(CPEComponentSimple._pct_encode_uri(nxtchar))
idx += 1
continue
# Bind the unquoted '?' special character to "%01".
if (thischar == "?"):
result.append("%01")
# Bind the unquoted '*' special character to "%02".
if (thischar == "*"):
result.append("%02")
idx += 1
return "".join(result)
[docs] def as_wfn(self):
"""
Returns the value of component encoded as Well-Formed Name (WFN)
string.
:returns: WFN string associated with component
:rtype: string
"""
return self._standard_value
[docs] def get_value(self):
"""
Returns the encoded value of component.
:returns: The encoded value of component
:rtype: string
"""
return self._encoded_value
[docs] def set_value(self, comp_str, comp_att):
"""
Set the value of component. By default, the component has a simple
value.
:param string comp_str: new value of component
:param string comp_att: attribute associated with value of component
:returns: None
:exception: ValueError - incorrect value of component
"""
old_value = self._encoded_value
self._encoded_value = comp_str
# Check the value of component
try:
self._parse(comp_att)
except ValueError:
# Restore old value of component
self._encoded_value = old_value
raise
# Convert encoding value to standard value (WFN)
self._decode()
if __name__ == "__main__":
import doctest
doctest.testmod()
doctest.testfile('../tests/testfile_cpecomp_simple.txt')