1
# -*- test-case-name: openid.test.test_xri -*-
2
"""Utility functions for handling XRIs.
4
@see: XRI Syntax v2.0 at the U{OASIS XRI Technical Committee<http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xri>}
9
XRI_AUTHORITIES = ['!', '=', '@', '+', '$', '(']
52
_escapeme_re = re.compile('[%s]' % (''.join(
53
map(lambda (m, n): u'%s-%s' % (unichr(m), unichr(n)),
54
UCSCHAR + IPRIVATE)),))
57
def identifierScheme(identifier):
58
"""Determine if this identifier is an XRI or URI.
60
@returns: C{"XRI"} or C{"URI"}
62
if identifier.startswith('xri://') or identifier[0] in XRI_AUTHORITIES:
69
"""Transform an XRI to IRI-normal form."""
70
if not xri.startswith('xri://'):
72
return escapeForIRI(xri)
75
_xref_re = re.compile('\((.*?)\)')
78
def _escape_xref(xref_match):
79
"""Escape things that need to be escaped if they're in a cross-reference.
81
xref = xref_match.group()
82
xref = xref.replace('/', '%2F')
83
xref = xref.replace('?', '%3F')
84
xref = xref.replace('#', '%23')
88
def escapeForIRI(xri):
89
"""Escape things that need to be escaped when transforming to an IRI."""
90
xri = xri.replace('%', '%25')
91
xri = _xref_re.sub(_escape_xref, xri)
96
"""Transform an XRI to URI normal form."""
97
return iriToURI(toIRINormal(xri))
100
def _percentEscapeUnicode(char_match):
101
c = char_match.group()
102
return ''.join(['%%%X' % (ord(octet),) for octet in c.encode('utf-8')])
106
"""Transform an IRI to a URI by escaping unicode."""
107
# According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
108
return _escapeme_re.sub(_percentEscapeUnicode, iri)
111
def providerIsAuthoritative(providerID, canonicalID):
112
"""Is this provider ID authoritative for this XRI?
116
# XXX: can't use rsplit until we require python >= 2.4.
117
lastbang = canonicalID.rindex('!')
118
parent = canonicalID[:lastbang]
119
return parent == providerID
122
def rootAuthority(xri):
123
"""Return the root authority for an XRI.
127
rootAuthority("xri://@example") == "xri://@"
132
if xri.startswith('xri://'):
134
authority = xri.split('/', 1)[0]
135
if authority[0] == '(':
137
# XXX: This is incorrect if someone nests cross-references so there
138
# is another close-paren in there. Hopefully nobody does that
139
# before we have a real xriparse function. Hopefully nobody does
141
root = authority[:authority.index(')') + 1]
142
elif authority[0] in XRI_AUTHORITIES:
143
# Other XRI reference.
146
# IRI reference. XXX: Can IRI authorities have segments?
147
segments = authority.split('!')
148
segments = reduce(list.__add__,
149
map(lambda s: s.split('*'), segments))
156
"""An XRI object allowing comparison of XRI.
158
Ideally, this would do full normalization and provide comparsion
159
operators as per XRI Syntax. Right now, it just does a bit of
160
canonicalization by ensuring the xri scheme is present.
162
@param xri: an xri string
165
if not xri.startswith('xri://'):