Ich kann es mir nicht erklären, warum es mir die meisten nicht zutrauen, etwas richtig gutes in Python zu bauen. (Die meisten Chefs halten es ohnehin für einen Witz, wenn man über diese smarte Programmiersprache spricht. Dabei ist sie genauso einfach wie PHP und stark wie Java.) Ich bin auf das folgende Problem beim entwickeln eines intelligenten directory listeners für lightty (ein leichtgewichtiger Webserver) auf meinem SheevaPlug (embedded linux/ubuntu on ARMv5TEL). Das Dateisystem wird in UTF-8 gelesen, darf aber nur als String an den WSGIServer übergeben werden. So müssen alle Unicode-Zeichen in HTML-entities bzw. url-encoding umgewandelt werden. Lösungen für dieses Problem sind im Netz leider rar. So lag es nahe, selber etwas zu entwickeln. Und das ist etwas richtig schickes!

Wie wandle ich unicode in URL-Kodierung?

def unicode2html(str):
	ret=""
	for ch in unicode(str):
		i = ord(ch)
		if i > 127:
			ret=ret+u'&#'+unicode(i)+';'
		else:
			ret=ret+ch
	return ret

Alles bis auf Klein- und Großbuchstaben wird in „%XY“ umgewandelt. Man nennt es in PHP auch „rawurlencode“, weil man keine Rücksicht auf Leerzeichen etc nimmt, die als „+“ kodiert werden dürfen.

Was kann man damit kodieren?

  • Parameternamen
  • Parameterwerte
  • Dateinamen
  • Ordnernamen

…also einzelne Teile einer URL. Übergibt man ein „http://…/etwas?abc=123“, wird alles ausnahmslos kodiert, so dass man die URL kaum wieder erkennt.

Wie wandle ich einen URL-kodierten String wieder zurück?


import re
def url2unicode(str):
	try:
		p = re.compile('.*(%([0-9A-F]{2,2})%([0-9A-F]{2,2})%([0-9A-F]{2,2})%([0-9A-F]{2,2})).*',re.I+re.S)
		while p.match(str):
			m = p.match(str)
			str = str.replace(m.group(1), (chr(int(m.group(2),16))+chr(int(m.group(3), 16)+int(m.group(4),16))+chr(int(m.group(5), 16))).decode('utf-8'))
	except:
		pass
	
	try:
		p = re.compile('.*(%([0-9A-F]{2,2})%([0-9A-F]{2,2})%([0-9A-F]{2,2})).*',re.I+re.S)
		while p.match(str):
			m = p.match(str)
			str = str.replace(m.group(1), (chr(int(m.group(2),16))+chr(int(m.group(3), 16)+int(m.group(4),16))).decode('utf-8'))
	except:
		pass
	
	try:
		p = re.compile('.*(%([0-9A-F]{2,2})%([0-9A-F]{2,2})).*',re.I+re.S)
		while p.match(str):
			m = p.match(str)
			str = str.replace(m.group(1), (chr(int(m.group(2),16))+chr(int(m.group(3), 16))).decode('utf-8'))
		#return (chr(int(m.group(2),16))+chr(int(m.group(3), 16))).decode('utf-8')
	except:
		pass
	
	try:
		p = re.compile('.*(%([0-9A-F]{2,2})).*',re.I+re.S)
		while p.match(str):
			m = p.match(str)
			str = str.replace(m.group(1), (chr(int(m.group(2),16))).decode('utf-8'))
	except:
		pass
	
	return str

Warum so kompliziert? Nun, utf-8 kann 4, 3, 2 oder 1-Byte-Zeichen enthalten. (Wir wissen nicht welche vorkommen, also versuchen wir zunächst die längsten und dann die kürzeren durch unicode zu ersetzen.) Solange in einer Kombination „p.match(str)“ nicht null – also wahr – ist, muss man in diesem String ersetzungen vornehmen.

Es fehlt uns nur noch der dritte Nothelfer: unicode2html

def unicode2html(str):
	ret=""
	for ch in unicode(str):
		i = ord(ch)
		if i > 127:
			ret=ret+u'&#'+unicode(i)+';'
		elif i == 042:
			ret = ret+u'&uot;'
		elif i == 046:
			ret = ret+u'&'
		elif i == 074:
			ret = ret+u'<'
		elif i == 076:
			ret = ret+u'>'
		else:
			ret=ret+ch
	return ret

Bis aus & > < und " ersetzt er keine Zeichen Unterhalb vom 127 (im ASCII), dafür aber alle oberhalb in &#NUMMER;. So kann man notfalls schnell zurück umwandeln.

Wenn Sie also irgendwo im Saarland einen netten Python-Job für mich haben… Gerne!


Schönen Dank an die folgenden Ideengeber: