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 (
63 identifier
and identifier
[0] in XRI_AUTHORITIES
):
70 """Transform an XRI to IRI-normal form."""
71 if not xri
.startswith('xri://'):
73 return escapeForIRI(xri
)
76 _xref_re
= re
.compile('\((.*?)\)')
79 def _escape_xref(xref_match
):
80 """Escape things that need to be escaped if they're in a cross-reference.
82 xref
= xref_match
.group()
83 xref
= xref
.replace('/', '%2F')
84 xref
= xref
.replace('?', '%3F')
85 xref
= xref
.replace('#', '%23')
89 def escapeForIRI(xri
):
90 """Escape things that need to be escaped when transforming to an IRI."""
91 xri
= xri
.replace('%', '%25')
92 xri
= _xref_re
.sub(_escape_xref
, xri
)
97 """Transform an XRI to URI normal form."""
98 return iriToURI(toIRINormal(xri
))
101 def _percentEscapeUnicode(char_match
):
102 c
= char_match
.group()
103 return ''.join(['%%%X' % (ord(octet
),) for octet
in c
.encode('utf-8')])
107 """Transform an IRI to a URI by escaping unicode."""
108 # According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
109 return _escapeme_re
.sub(_percentEscapeUnicode
, iri
)
112 def providerIsAuthoritative(providerID
, canonicalID
):
113 """Is this provider ID authoritative for this XRI?
117 # XXX: can't use rsplit until we require python >= 2.4.
118 lastbang
= canonicalID
.rindex('!')
119 parent
= canonicalID
[:lastbang
]
120 return parent
== providerID
123 def rootAuthority(xri
):
124 """Return the root authority for an XRI.
128 rootAuthority("xri://@example") == "xri://@"
133 if xri
.startswith('xri://'):
135 authority
= xri
.split('/', 1)[0]
136 if authority
[0] == '(':
138 # XXX: This is incorrect if someone nests cross-references so there
139 # is another close-paren in there. Hopefully nobody does that
140 # before we have a real xriparse function. Hopefully nobody does
142 root
= authority
[:authority
.index(')') + 1]
143 elif authority
[0] in XRI_AUTHORITIES
:
144 # Other XRI reference.
147 # IRI reference. XXX: Can IRI authorities have segments?
148 segments
= authority
.split('!')
149 segments
= reduce(list.__add
__,
150 map(lambda s
: s
.split('*'), segments
))
157 """An XRI object allowing comparison of XRI.
159 Ideally, this would do full normalization and provide comparsion
160 operators as per XRI Syntax. Right now, it just does a bit of
161 canonicalization by ensuring the xri scheme is present.
163 @param xri: an xri string
166 if not xri
.startswith('xri://'):