2010-03-16 4 views
12

Zuerst etwas Hintergrund: Ich entwickle eine Webanwendung mit Python. Alle meine (Text-) Dateien sind derzeit in UTF-8 mit der Stückliste gespeichert. Dies beinhaltet alle meine HTML-Templates und CSS-Dateien. Diese Ressourcen werden als binäre Daten (Stückliste und alle) in meiner Datenbank gespeichert.UTF-8 HTML- und CSS-Dateien mit Stückliste (und wie Sie die Stückliste mit Python entfernen)

Wenn ich die Vorlagen aus der DB abrufen, dekodiere ich sie mit template.decode('utf-8'). Wenn der HTML-Code im Browser eintrifft, ist die BOM am Anfang des HTTP-Antworthauptteils vorhanden. Dies erzeugt einen sehr interessanten Fehler in Chrome:

Extra <html> encountered. Migrating attributes back to the original <html> element and ignoring the tag.

Chrome scheint automatisch einen <html>-Tag zu erzeugen, wenn es die Stückliste und Fehler es für Inhalt sieht, ein Fehler bei dem echten <html> Tag zu machen.

Also, was ist der beste Weg mit Python, die Stückliste aus meinen UTF-8-kodierten Vorlagen zu entfernen (wenn es existiert - kann ich das in Zukunft nicht garantieren)?

Bei anderen textbasierten Dateien wie CSS interpretieren die wichtigsten Browser die Stückliste korrekt (oder ignorieren sie)? Sie werden als reine Binärdaten ohne .decode('utf-8') gesendet.

Hinweis: Ich verwende Python 2.5.

Danke!

Antwort

23

Da Sie angeben:

Alle meine (Text-Dateien) sind zur Zeit in UTF-8 gespeichert mit der BOM

dann mit dem 'utf-8-sig' Codec sie entschlüsseln:

>>> s = u'Hello, world!'.encode('utf-8-sig') 
>>> s 
'\xef\xbb\xbfHello, world!' 
>>> s.decode('utf-8-sig') 
u'Hello, world!' 

Es entfernt automatisch die erwartete Stückliste und funktioniert einwandfrei, wenn die BOM nicht so gut präsentieren.

+0

Ooh! Sehr schön! Ich werde es versuchen, sobald ich kann. – Cameron

+0

Funktioniert wunderbar (obwohl Chrome mysteriöserweise den Fehler nicht mehr gab, selbst mit meinem alten (falschen) Code - das ist es, was ich bekomme, wenn ich eine ganze Reihe von Änderungen gleichzeitig mache). – Cameron

0

können Sie etwas ähnliches verwenden, um BOM zu entfernen:

import os, codecs 
def remove_bom_from_file(filename, newfilename): 
    if os.path.isfile(filename): 
     # open file 
     f = open(filename,'rb') 

     # read first 4 bytes 
     header = f.read(4) 

     # check if we have BOM... 
     bom_len = 0 
     encodings = [ (codecs.BOM_UTF32, 4), 
      (codecs.BOM_UTF16, 2), 
      (codecs.BOM_UTF8, 3) ] 

     # ... and remove appropriate number of bytes  
     for h, l in encodings: 
      if header.startswith(h): 
       bom_len = l 
       break 
     f.seek(0) 
     f.read(bom_len) 

     # copy the rest of file 
     contents = f.read() 
     nf = open(newfilename) 
     nf.write(contents) 
     nf.close() 
+1

Hmm, müssen Sie die Datei nach dem Lesen der ersten 4 Bytes und vor dem Testen auf Stücklisten nicht zurückspulen? 'f.seek (0)'. –

+0

@Konrad Ich habe das verpasst, danke für den Hinweis. Dies ist sowieso kein Produktionscode:]. – pajton

+0

Sieht gut aus (mit dem 'seek (0)' fix), aber ich habe bereits die gesamte Datei im Speicher, wenn ich versuche, die BOM zu zerlegen - wie effizient ist Inhalt [2:] (zum Beispiel) in Python? Erstellt es eine Kopie der gesamten Zeichenfolge? – Cameron

10

das erste Zeichen Überprüfen Sie nach der Decodierung, um zu sehen, ob es die BOM ist:

if u.startswith(u'\ufeff'): 
    u = u[1:] 
+0

Wird am Anfang einer Nicht-UTF-8-Datei "u '\ ufffe" auftreten? Würde die BOM nicht zwei "Zeichen" (gelesen: Bytes) in meinem Fall (UTF-8) nehmen? – Cameron

+0

'u '\ ufffe' kann am Anfang jeder UTF- oder UCS-kodierten Datei gefunden werden. Die BOM ist drei Byte in UTF-8, aber es ist immer noch ein einziger Unicode-Codepunkt. –

+0

OK, um das zu verdeutlichen, müsste ich zuerst den Byte-Inhalt der Datei mit 'u = contents.decode ('utf-8') dekodieren und dann kann ich Ihre Methode verwenden weil die Stückliste ein einzelner Codepoint ist. Richtig? – Cameron

1

Die zuvor akzeptierte Antwort falsch ist.

u'\ufffe' ist kein Zeichen. Wenn Sie es in einer Unicode-Saite bekommen, hat jemand mächtig gestopft.

Die BOM ist (auch bekannt als ZERO WIDTH NO-BREAK SPACE) u'\ufeff'

>>> UNICODE_BOM = u'\N{ZERO WIDTH NO-BREAK SPACE}' 
>>> UNICODE_BOM 
u'\ufeff' 
>>> 

lesen this (für BOM Strg-F-Suche) und this und this (Strg-F Suche nach BOM).

Hier ist eine richtige und Typo/braino feste Antwort:

Decode Ihre Eingabe in unicode_str. Dann tun Sie dies:

# If I mistype the following, it's very likely to cause a SyntaxError. 
UNICODE_BOM = u'\N{ZERO WIDTH NO-BREAK SPACE}' 
if unicode_str and unicode_str[0] == UNICODE_BOM: 
    unicode_str = unicode_str[1:] 

Bonus: mit einer benannte Konstante Ihrer Leser ein bisschen mehr von einem Anhaltspunkt gibt, was los ist, als dies eine Sammlung von scheinbar willkürlichen hexoglyphics.

Update Leider gibt es in der Python-Standardbibliothek keine passende benannte Konstante.

Leider stellt die Codecs Modul nur „eine Schlinge und eine Täuschung“:

>>> import pprint, codecs 
>>> pprint.pprint([(k, getattr(codecs, k)) for k in dir(codecs) if k.startswith('BOM')]) 
[('BOM', '\xff\xfe'), #### aarrgghh!! #### 
('BOM32_BE', '\xfe\xff'), 
('BOM32_LE', '\xff\xfe'), 
('BOM64_BE', '\x00\x00\xfe\xff'), 
('BOM64_LE', '\xff\xfe\x00\x00'), 
('BOM_BE', '\xfe\xff'), 
('BOM_LE', '\xff\xfe'), 
('BOM_UTF16', '\xff\xfe'), 
('BOM_UTF16_BE', '\xfe\xff'), 
('BOM_UTF16_LE', '\xff\xfe'), 
('BOM_UTF32', '\xff\xfe\x00\x00'), 
('BOM_UTF32_BE', '\x00\x00\xfe\xff'), 
('BOM_UTF32_LE', '\xff\xfe\x00\x00'), 
('BOM_UTF8', '\xef\xbb\xbf')] 
>>> 

Update 2 Wenn Sie noch nicht Ihre Eingabe decodiert und wollen es für eine Stückliste zu überprüfen, müssen Sie Überprüfen Sie für TWO verschiedene Stücklisten für UTF-16 und mindestens TWO verschiedene Stücklisten für UTF-32. Wenn es nur jeweils einen Weg gäbe, würden Sie keine Stückliste benötigen, oder?

hier wörtlich ungeschönte aus meinem eigenen Code ist meine Lösung für dieses Problem:

def check_for_bom(s): 
    bom_info = (
     ('\xFF\xFE\x00\x00', 4, 'UTF-32LE'), 
     ('\x00\x00\xFE\xFF', 4, 'UTF-32BE'), 
     ('\xEF\xBB\xBF',  3, 'UTF-8'), 
     ('\xFF\xFE',   2, 'UTF-16LE'), 
     ('\xFE\xFF',   2, 'UTF-16BE'), 
     ) 
    for sig, siglen, enc in bom_info: 
     if s.startswith(sig): 
      return enc, siglen 
    return None, 0 

Der Eingang s sollte mindestens die ersten 4 Bytes Ihrer eingegeben werden. Es gibt die Codierung zurück, die zum Dekodieren des Post-BOM-Teils Ihrer Eingabe verwendet werden kann, zuzüglich der Länge der Stückliste (falls vorhanden).

Wenn Sie paranoid sind, könnten Sie weitere 2 (nicht standardmäßige) UTF-32-Ordnungen zulassen, aber Python liefert keine Kodierung für sie und ich habe noch nie von einem tatsächlichen Vorkommen gehört. t stören.

+0

Ich sehe nicht, wie "ZERO WIDTH NO-BREAK SPACE", hier verwendet, weil es auch die BOM (Wortspiel gemeint) ist, mehr lesbar als u "\ uFFEFF" ist. Beide erfordern, dass Vorwissen über die Stückliste verstanden wird. – Cameron

+0

@Cameron: Die Lesbarkeit kommt von der Angabe der Konstante, die Sie einen Namen verwenden, z. UNICODE_BOM. –

+0

@Cameron: Ich weiß nichts über die Stückliste, aber ich habe ein Gespür dafür, was ein "Zero Wide No-Break Space" ist, und keine Ahnung, was ein "UFFEF" ist. Letzteres ist auch schwieriger, sicher zu sein, dass ich richtig getippt habe, da seine 8 Zeichenlänge nur aus 3 alphanumerischen Zeichen besteht, von denen zwei einander sehr ähnlich sind. –