Données SQLite, python, unicode et non-utf

j'ai commencé par essayer de stocker les chaînes en sqlite en utilisant python, et j'ai eu le message:

sqlite3.ProgrammingError: vous devez n'utilisez pas de bytestrings 8-bit à moins que vous utiliser un text_factory qui peut interpréter Bytestrings 8-bit (like text_factory = Str.) Il est fortement recommandé que au contraire, vous venez de passer votre application aux chaînes Unicode.

Ok, je suis passé aux chaînes Unicode. Puis j'ai commencé à réception du message:

sqlite3.Opérationalerror: impossible décoder à la colonne UTF-8 "tag_artist" avec le texte "Sigur Rós "

en essayant de récupérer des données à partir de la base de données. Plus de recherche et j'ai commencé à l'encoder dans utf8, mais alors 'Sigur Rós' commence à ressembler à 'Sigur RÃ3s '

note: ma console a été réglée pour afficher "latin_1" comme @John Machin l'a souligné.

qu'est-ce qui se passe? Après avoir lu ce , décrivant exactement la même situation que moi, il semble que le Conseil est d'ignorer les autres conseils et d'utiliser 8-bit bytstrings après tout.

Je ne savais pas grand chose sur unicode et utf avant de commencer ce processus. J'ai appris pas mal de choses ces dernières heures, mais je ne sais toujours pas s'il y a un moyen de convertir correctement " ó " du latin-1 à utf-8 et de ne pas le détruire. S'il n'y en a pas, pourquoi est-ce que sqlite 'recommande fortement' que je change mon application en chaînes unicode?


je vais mettre à jour cette question avec un résumé et un exemple de code de tout ce que j'ai appris au cours des dernières 24 heures afin que quelqu'un à ma place puisse avoir un(er) guide facile. Si les informations que je poste sont fausses ou trompeuses en aucune façon s'il Vous Plaît me dire et je vais mettre à jour, ou l'un de vous les gars seniors peuvent mettre à jour.


résumé des réponses

laissez-moi d'abord énoncer le but tel que je le comprends. Le but dans le traitement des encodages Divers, si vous essayez de convertir entre eux, est de comprendre ce qu'est votre encodage source, puis le convertir en unicode en utilisant ce codage source, puis le convertir à votre encodage désiré. Unicode est une base et les encodages sont des mappages de sous-ensembles de cette base. utf_8 a de la place pour chaque caractère unicode, mais parce qu'ils ne sont pas au même endroit que, par exemple, latin_1, une chaîne codée en utf_8 et envoyée à une console latin_1 ne ressemblera pas à ce que vous attendez. En python, le processus d'accès à unicode et dans un autre encodage ressemble à:

str.decode('source_encoding').encode('desired_encoding')

ou si la DOD est déjà en unicode

str.encode('desired_encoding')

pour sqlite Je n'ai pas vraiment voulu l'encoder à nouveau, je voulais le décoder et le laisser dans le format unicode. Voici quatre des choses dont vous devez être conscient lorsque vous essayez de travailler avec unicode et les encodages en python.

  1. L'encodage de la chaîne que vous souhaitez travailler, et l'encodage que vous souhaitez obtenir.
  2. le codage du système.
  3. l'encodage de la console.
  4. l'encodage du fichier source

Élaboration:

(1) Lorsque vous lisez une chaîne de caractères d'une source, il doit y avoir un encodage, comme latin_1 ou utf_8. Dans mon cas, je reçois des chaînes de noms de fichiers, donc malheureusement, je pourrais avoir n'importe quel genre d'encodage. Windows XP utilise UCS-2 (Un système Unicode) comme type de chaîne de caractères natif, ce qui semble être une tricherie pour moi. Heureusement pour moi, les caractères dans la plupart des noms de fichiers ne vont pas être constitués de plus d'un type d'encodage source, et je pense que tous les miens étaient soit complètement latin_1, complètement utf_8, ou tout simplement ASCII (qui est un sous-ensemble de deux de ceux-ci). Je les ai juste lus et décodés comme s'ils étaient encore en latin_1 ou utf_8. Il est possible, cependant, que vous puissiez avoir latin_1 et utf_8 et n'importe quels autres caractères mélangés ensemble dans un nom de fichier sur Windows. Parfois ces caractères peuvent apparaître comme des boîtes, d'autres fois ils ont juste l'air mutilé, et d'autres fois ils semblent corrects (caractères accentués et autres). Aller de l'avant.

(2) Python a un encodage de système par défaut qui est défini quand python démarre et ne peut pas être changé pendant l'exécution. Voir ici pour plus de détails. Sale résumé ... voici le fichier que j'ai ajouté:

# sitecustomize.py  
# this file can be anywhere in your Python path,  
# but it usually goes in ${pythondir}/lib/site-packages/  
import sys  
sys.setdefaultencoding('utf_8')  

cet encodage système est celui qui est utilisé lorsque vous utilisez la fonction unicode ("str") sans aucun autre paramètre d'encodage. Pour dire cela d'une autre manière, python essaie de décoder "str" à unicode en se basant sur l'encodage système par défaut.

(3) Si vous utilisez le Python en ligne de commande, je pense que votre console s'affichera en fonction de l'encodage système par défaut. J'utilise pydev avec eclipse pour une raison quelconque, donc j'ai dû aller dans les paramètres de mon projet, Éditer les propriétés de configuration de lancement de mon script de test, aller dans l'onglet commun, et changer la console de latin-1 à utf-8 pour que je puisse confirmer visuellement ce que je faisais fonctionnait.

(4) Si vous voulez avoir quelques chaînes de test, par exemple

test_str = "ó"

dans votre code source, alors vous devrez dire à python quel type d'encodage vous utilisez dans ce fichier. (FYI: quand j'ai mal tapé un encodage, j'ai dû ctrl-Z parce que mon fichier est devenu illisible.) Ceci est facilement accompli en mettant une ligne comme en haut de votre fichier de code source:

# -*- coding: utf_8 -*-

si vous ne disposez pas de cette information, python tente de parser votre code ascii par défaut, et donc:

SyntaxError: Non-ASCII character 'xf3' in file _redacted_ on line 81, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

une fois votre le programme fonctionne correctement, ou, si vous n'utilisez pas la console de python ou n'importe quelle autre console pour regarder la sortie, alors vous ne vous soucierez probablement que du #1 sur la liste. La valeur par défaut du système et l'encodage de la console ne sont pas très importants, sauf si vous devez regarder la sortie et/ou si vous utilisez la fonction builtin unicode () (sans aucun paramètre d'encodage) à la place de la chaîne.decode() fonction. J'ai écrit une fonction démo que je vais coller au fond de ce gâchis gigantesque que j'espère correctement démontre les éléments dans ma liste. Voici une partie de la sortie lorsque j'exécute le caractère " ó " à travers la fonction de démonstration, montrant comment diverses méthodes réagissent au caractère en tant qu'entrée. L'encodage de mon système et la sortie de la console sont tous les deux définis à utf_8 pour cette exécution:

'�' = original char <type 'str'> repr(char)='xf3'
'?' = unicode(char) ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data
'ó' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

maintenant je vais changer le codage du système et de la console en latin_1, et j'obtiens cette sortie pour la même entrée:

'ó' = original char <type 'str'> repr(char)='xf3'
'ó' = unicode(char) <type 'unicode'> repr(unicode(char))=u'xf3'
'ó' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

notez que le caractère "original" affiche correctement et la builtin unicode() fonctionne maintenant.

maintenant je change la sortie de ma console en utf_8.

'�' = original char <type 'str'> repr(char)='xf3'
'�' = unicode(char) <type 'unicode'> repr(unicode(char))=u'xf3'
'�' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

ici tout fonctionne toujours comme la dernière fois, mais la console ne peut pas afficher la sortie correctement. Etc. La fonction ci-dessous affiche également plus d'informations que cela et j'espère aidant les gens à comprendre où l'écart dans leur compréhension. Je sais que toutes ces informations sont dans d'autres endroits et plus à fond traité là-bas, mais j'espère que ce serait un bon point de lancement pour quelqu'un qui essaie d'obtenir le codage avec python et/ou sqlite. Les idées sont grandes, mais parfois le code source peut vous sauver un jour ou deux d'essayer de comprendre quelles fonctions font quoi.

Avertissements: je ne suis pas d'encodage expert, j'ai mis cela ensemble à l'aide de ma propre compréhension. J'ai continué à construire sur elle quand j'aurais probablement commencé à passer des fonctions comme arguments pour éviter autant de code redondant, donc si je peux Je vais le rendre plus concis. De plus, utf_8 et latin_1 ne sont en aucun cas les seuls schémas d'encodage, ce sont juste les deux avec lesquels je jouais parce que je pense qu'ils gèrent tout ce dont j'ai besoin. Ajoutez vos propres schémas d'encodage à la fonction de démonstration et testez vos propres entrées.

une autre chose: il ya apparemment les développeurs d'applications fous rendre la vie difficile dans Windows.

#!/usr/bin/env python
# -*- coding: utf_8 -*-

import os
import sys

def encodingDemo(str):
    validStrings = ()
    try:        
        print "str =",str,"{0} repr(str) = {1}".format(type(str), repr(str))
        validStrings += ((str,""),)
    except UnicodeEncodeError as ude:
        print "Couldn't print the str itself because the console is set to an encoding that doesn't understand some character in the string.  See error:nt",
        print ude
    try:
        x = unicode(str)
        print "unicode(str) = ",x
        validStrings+= ((x, " decoded into unicode by the default system encoding"),)
    except UnicodeDecodeError as ude:
        print "ERROR.  unicode(str) couldn't decode the string because the system encoding is set to an encoding that doesn't understand some character in the string."
        print "tThe system encoding is set to {0}.  See error:nt".format(sys.getdefaultencoding()),  
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the unicode(str) because the console is set to an encoding that doesn't understand some character in the string.  See error:nt",
        print uee
    try:
        x = str.decode('latin_1')
        print "str.decode('latin_1') =",x
        validStrings+= ((x, " decoded with latin_1 into unicode"),)
        try:        
            print "str.decode('latin_1').encode('utf_8') =",str.decode('latin_1').encode('utf_8')
            validStrings+= ((x, " decoded with latin_1 into unicode and encoded into utf_8"),)
        except UnicodeDecodeError as ude:
            print "The string was decoded into unicode using the latin_1 encoding, but couldn't be encoded into utf_8.  See error:nt",
            print ude
    except UnicodeDecodeError as ude:
        print "Something didn't work, probably because the string wasn't latin_1 encoded.  See error:nt",
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the str.decode('latin_1') because the console is set to an encoding that doesn't understand some character in the string.  See error:nt",
        print uee
    try:
        x = str.decode('utf_8')
        print "str.decode('utf_8') =",x
        validStrings+= ((x, " decoded with utf_8 into unicode"),)
        try:        
            print "str.decode('utf_8').encode('latin_1') =",str.decode('utf_8').encode('latin_1')
        except UnicodeDecodeError as ude:
            print "str.decode('utf_8').encode('latin_1') didn't work.  The string was decoded into unicode using the utf_8 encoding, but couldn't be encoded into latin_1.  See error:nt",
            validStrings+= ((x, " decoded with utf_8 into unicode and encoded into latin_1"),)
            print ude
    except UnicodeDecodeError as ude:
        print "str.decode('utf_8') didn't work, probably because the string wasn't utf_8 encoded.  See error:nt",
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the str.decode('utf_8') because the console is set to an encoding that doesn't understand some character in the string.  See error:nt",uee

    print
    print "Printing information about each character in the original string."
    for char in str:
        try:
            print "t'" + char + "' = original char {0} repr(char)={1}".format(type(char), repr(char))
        except UnicodeDecodeError as ude:
            print "t'?' = original char  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(char), repr(char), ude)
        except UnicodeEncodeError as uee:
            print "t'?' = original char  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(char), repr(char), uee)
            print uee    

        try:
            x = unicode(char)        
            print "t'" + x + "' = unicode(char) {1} repr(unicode(char))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "t'?' = unicode(char) ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "t'?' = unicode(char)  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        try:
            x = char.decode('latin_1')
            print "t'" + x + "' = char.decode('latin_1') {1} repr(char.decode('latin_1'))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "t'?' = char.decode('latin_1')  ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "t'?' = char.decode('latin_1')  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        try:
            x = char.decode('utf_8')
            print "t'" + x + "' = char.decode('utf_8') {1} repr(char.decode('utf_8'))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "t'?' = char.decode('utf_8')  ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "t'?' = char.decode('utf_8')  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        print

x = 'ó'
encodingDemo(x)

merci Beaucoup pour les réponses ci-dessous et surtout à @John Machin pour avoir répondu si minutieusement.

64
demandé sur shuttle87 2010-03-06 17:15:42

5 réponses

je suis toujours ignorant de savoir s'il y a un moyen de convertir correctement "ó" du latin-1 à utf-8 et non de le saboter

repr() et unicodedata.nom() sont vos amis quand il s'agit de débogage des problèmes tels:

>>> oacute_latin1 = "\xF3"
>>> oacute_unicode = oacute_latin1.decode('latin1')
>>> oacute_utf8 = oacute_unicode.encode('utf8')
>>> print repr(oacute_latin1)
'\xf3'
>>> print repr(oacute_unicode)
u'\xf3'
>>> import unicodedata
>>> unicodedata.name(oacute_unicode)
'LATIN SMALL LETTER O WITH ACUTE'
>>> print repr(oacute_utf8)
'\xc3\xb3'
>>>

si vous envoyez oacute_utf8 à un terminal qui est configuré pour latin1, vous obtiendrez A-tilde suivi de superscript-3.

je suis passé aux chaînes Unicode.

comment appelez-vous les chaînes Unicode? UTF-16?

qu'est-ce qui se passe? Après avoir lu ceci, décrivant exactement la même situation dans laquelle je suis, il semble que le Conseil est d'ignorer les autres conseils et d'utiliser 8-bit bytstrings après tout.

Je ne peux pas imaginer comment il vous semble. L'histoire qui était véhiculée était que les objets unicode en Python et encodage UTF-8 dans la base de données étaient la manière aller. Cependant Martin a répondu à la question initiale, en donnant une méthode ("text factory") pour que L'OP puisse utiliser la latin1 -- cela ne constituait pas une recommandation!

mise à Jour en réponse à ces autres questions soulevées dans un commentaire:

Je n'ai pas compris que les caractères unicode contiennent encore un encodage implicite. Suis-je en train de dire que le droit?

Pas de. Un l'encodage est un mappage entre Unicode et autre chose, et vice versa. Un caractère Unicode n'a pas d'encodage, implicite ou autre.

il me semble comme unicode ("\xF3") et"\xF3".decode('latin1") sont les mêmes lors de l'évaluation avec les repr().

dire quoi? Il ne me semble pas:

>>> unicode("\xF3")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf3 in position 0: ordinal
not in range(128)
>>> "\xF3".decode('latin1')
u'\xf3'
>>>

peut-être voulez-vous dire u'\xf3' == '\xF3'.decode('latin1') ... c'est certainement vrai.

il est également vrai que unicode(str_object, encoding) fait la même chose que str_object.decode(encoding) ... y compris l'explosion quand un codage inapproprié est fourni.

est qu'une heureuse circonstance

Que les 256 premiers caractères Unicode sont les mêmes, du code, comme les 256 caractères latin1 est une bonne idée. Parce que les 256 caractères latin1 possibles sont mappés en Unicode, cela signifie que N'importe quel octet 8 bits, n'importe quel objet Python str peut être décodé en unicode sans exception. C'est ainsi que cela devrait être.

cependant il existe certaines personnes qui confondent deux concepts tout à fait distincts: "mon script court jusqu'à l'achèvement sans aucune exception étant soulevée" et "mon script est sans erreur". Pour eux, latin1 est "un piège et une illusion".

en d'autres termes, si vous avez un fichier qui est en fait encodé dans cp1252 ou gbk ou koi8-u ou autre et que vous le décodez en utilisant latin1, le Unicode résultant sera utter rubbish et Python (ou n'importe quel autre langage) ne signalera pas une erreur -- il n'a aucun moyen de savoir que vous avez commis une sottise.

ou unicode("str") va toujours le bon décodage?

tout comme cela, avec l'encodage par défaut étant ascii, il retournera le bon unicode si le fichier est effectivement encodé en ASCII. Sinon, ça va exploser.

De même, si vous spécifiez le bon encodage, ou un super-ensemble du bon encodage, vous obtiendrez le bon résultat. Sinon, vous aurez du charabia ou une exception.

en résumé: la réponse est non.

si ce n'est pas le cas, quand je reçois un str de python qui contient un jeu de caractères possible, Comment puis-je le décoder?

si l'objet str est un document XML valide, il sera spécifié devant. Par défaut est UTF-8. Si c'est une page web correctement construite, elle doit être spécifiée à l'avance (cherchez "charset"). Malheureusement, de nombreux auteurs de pages web mentent à travers leurs dents (ISO-8859-1 alias latin1, devrait être Windows-1252 alias cp1252; ne gaspillez pas de ressources en essayant de décoder gb2312, utilisez gbk à la place). Vous pouvez obtenir des indices à partir de la nationalité/langue du site web.

UTF-8 vaut toujours la peine d'être essayé. Si les données ascii, ça marchera très bien, parce que l'ascii est un sous-ensemble de utf8. Une chaîne de texte qui a été écrite en utilisant des caractères non-ascii et a été encodée dans un encodage autre que utf8 échouera presque certainement avec une exception si vous essayez de la décoder comme utf8.

toutes les heuristiques ci-dessus et plus et beaucoup de statistiques sont encapsulés dans chardet , un module pour deviner l'encodage de fichiers arbitraires. Il fonctionne généralement bien. Cependant, vous ne pouvez pas faire le logiciel idiot-proof. Par exemple, si vous fichiers de données concaténés écrits certains avec l'encodage A et certains avec l'encodage B, et de transmettre le résultat à chardet, la réponse est probablement d'encoder C avec un niveau de confiance réduit par exemple 0.8. Vérifiez toujours la partie " confiance "de la réponse .

Si tout le reste échoue:

(1) Essayez de demander ici, avec un petit échantillon du devant de vos données ... print repr(your_data[:400]) ... et toutes les infos collatérales sur sa provenance que vous avez.

(2) des recherches russes récentes sur les techniques de récupération des mots de passe oubliés semblent tout à fait applicables à la déduction des encodages inconnus.

Update 2 BTW, n'est-il pas temps que vous avez ouvert une autre question ?- )

encore une chose: il y a apparemment des caractères que Windows utilise comme Unicode pour certains caractères qui ne sont pas le bon Unicode pour ce caractère, donc vous pouvez avoir à mapper ces caractères aux bons si vous voulez les utiliser dans d'autres programmes qui attendent ces caractères au bon endroit.

ce n'est pas Windows qui le fait; c'est une bande de développeurs d'applications fous. Vous auriez pu, de façon plus compréhensible, ne pas paraphraser, mais citer le premier paragraphe de l'article d'effbot auquel vous avez fait référence:

certaines demandes ajoutent CP1252 (Windows, Europe de l'Ouest) caractères des documents marqués ISO 8859-1 (Latin 1) ou autres codages. Ces caractères ne sont pas des caractères ISO-8859-1 valides, et peuvent causer toutes sortes de problèmes dans le traitement et l'affichage des applications.

Contexte:

la plage U+0000 à U+001F inclusivement est désignée en Unicode par"caractères de contrôle C0". Ceux-ci existent aussi en ASCII et en latin1, avec les mêmes significations. Ils comprennent de telles choses familiales comme un retour chariot, saut de ligne, bell, retour arrière, onglet, et d'autres qui sont rarement utilisées.

la plage U+0080 à U+009F inclusivement est désignée en Unicode par"C1 Control Characters". Ceux-ci existent aussi en latin1, et comprennent 32 caractères que personne à l'extérieur unicode.org peut imaginer n'importe quelle utilisation possible pour.

par conséquent, si vous exécutez un comptage de fréquence de caractères sur vos données unicode ou latin1, et que vous trouvez des caractères dans cette plage, vos données sont corrompre. Il n'y a pas de solution universelle; cela dépend de la façon dont elle est devenue corrompue. Les caractères may ont la même signification que les caractères cp1252 aux mêmes positions, et donc la solution de l'effbot fonctionnera. Dans un autre cas que j'ai examiné récemment, les caractères douteux semblent avoir été causés par la concaténation de fichiers textes encodés en UTF-8 et un autre encodage qui devait être déduit en fonction de la fréquence des lettres dans la langue (humaine) les fichiers ont été rédigé.

34
répondu John Machin 2012-04-23 21:08:30

UTF-8 est l'encodage par défaut des bases de données SQLite. Cela s'affiche dans des situations comme "SELECT CAST(x'52C3B373' AS TEXT);". Cependant, la bibliothèque C SQLite ne vérifie pas si une chaîne insérée dans un DB est valide UTF-8.

si vous insérez un objet Python unicode (ou un objet str en 3.x), la bibliothèque sqlite3 de Python le convertira automatiquement en UTF-8. Mais si vous insérez un objet str, il va juste supposer la chaîne est UTF-8, parce que Python 2.x "str" ne connaît pas son encodage. C'est une raison pour préférer les chaînes Unicode.

cependant, cela ne vous aide pas si vos données sont brisées pour commencer.

pour corriger vos données, faites

db.create_function('FIXENCODING', 1, lambda s: str(s).decode('latin-1'))
db.execute("UPDATE TheTable SET TextColumn=FIXENCODING(CAST(TextColumn AS BLOB))")

pour chaque colonne de texte dans votre base de données.

20
répondu dan04 2010-03-07 06:36:56

j'ai corrigé ce problème de pysqlite en réglant:

conn.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')

Par défaut text_factory est d'unicode(), qui utilisera la valeur par défaut de codage ascii (sur ma machine)

18
répondu hoju 2010-04-07 01:17:44

bien sûr qu'il y en a. Mais vos données sont déjà cassées dans la base de données, vous aurez donc besoin de le corriger:

>>> print u'Sigur Rós'.encode('latin-1').decode('utf-8')
Sigur Rós
7
répondu Ignacio Vazquez-Abrams 2010-03-06 14:30:58

mes problèmes unicode avec Python 2.x (Python 2.7.6 pour être spécifique) a corrigé ceci:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

Il a également résolu l'erreur de vous mentionner dès le début de l'après:

sqlite3.ProgrammingError: vous ne devez pas utiliser 8-bit bytestrings à moins que ...

MODIFIER

sys.setdefaultencoding est un sale hack . Oui, c' peut résoudre les problèmes UTF-8, mais tout a un prix. Pour plus de détails, consultez les liens suivants:

2
répondu Dušan Maďar 2017-05-23 11:54:38