2

J'ai un fichier JSON multiligne avec des enregistrements contenant des caractères spéciaux codés en hexadécimaux. Voici un exemple d'un seul enregistrement JSON:Décodage d'une chaîne avec des caractères spéciaux échappés dans Scala issue

{\x22value\x22:\x22\xC4\xB1arines Bint\xC4\xB1\xC3\xA7 Ramu\xC3\xA7lar\x22}

Cet enregistrement est censé être {"value":"ıarines Bintıç Ramuçlar"}, par exemple Les caractères '"' sont remplacés par les caractères hexadécimaux \ x22 correspondants et les autres caractères spéciaux Unicode sont remplacés par un ou deux hexadécimaux (par exemple \ xC3 \ xA7 encode ç, etc.)

Je dois convertir des chaînes similaires en Unicode normal chaîne à Scala, alors quand il a produit imprimé {"value":"ıarines Bintıç Ramuçlar"} sans hexadécimaux

en Python, je peux facilement décoder ces enregistrements avec une ligne de code.

>>> a = "{\x22value\x22:\x22\xC4\xB1arines Bint\xC4\xB1\xC3\xA7 Ramu\xC3\xA7lar\x22}" 
>>> a.decode("utf-8") 
u'{"value":"\u0131arines Bint\u0131\xe7 Ramu\xe7lar"}' 
>>> print a.decode("utf-8") 
{"value":"ıarines Bintıç Ramuçlar"} 

Mais Scala, je ne peux pas trouver un moyen de décoder J'ai essayé sans succès de le convertir comme ceci:

scala> val a = """{\x22value\x22:\x22\xC4\xB1arines Bint\xC4\xB1\xC3\xA7 Ramu\xC3\xA7lar\x22}""" 
scala> print(new String(a.getBytes(), "UTF-8")) 
{\x22value\x22:\x22\xC4\xB1arines Bint\xC4\xB1\xC3\xA7 Ramu\xC3\xA7lar\x22} 

J'ai essayé aussi URLDecoder comme je l'ai trouvé en solution pour problème similaire (mais avec l'URL):

scala> val a = """{\x22value\x22:\x22\xC4\xB1arines Bint\xC4\xB1\xC3\xA7 Ramu\xC3\xA7lar\x22}""" 
scala> print(java.net.URLDecoder.decode(a.replace("\\x", "%"), "UTF-8")) 
{"value":"ıarines Bintıç Ramuçlar"} 

Il a produit le résultat souhaité pour cet exemple mais ne semble pas sûr pour les champs de texte génériques puisqu'il Conçu pour fonctionner avec les URL et nécessite de remplacer tous les \x par % dans la chaîne.

Est-ce que Scala a une meilleure façon de résoudre ce problème?

Je suis nouveau à Scala et sera reconnaissant pour toute aide

MISE À JOUR: J'ai fait une solution personnalisée avec javax.xml.bind.DatatypeConverter.parseHexBinary. Cela fonctionne pour l'instant, mais il semble lourd et pas du tout élégant. Je pense qu'il devrait y avoir une façon plus simple de faire cela.

Voici le code:

import javax.xml.bind.DatatypeConverter 
import scala.annotation.tailrec 
import scala.util.matching.Regex 

def decodeHexChars(string: String): String = { 
    val regexHex: Regex = """\A\\[xX]([0-9a-fA-F]{1,2})(.*)""".r 
    def purgeBuffer(buffer: String, acc: List[Char]): List[Char] = { 
    if (buffer.isEmpty) acc 
    else new String(DatatypeConverter.parseHexBinary(buffer)).reverse.toList ::: acc 
    } 
    @tailrec 
    def traverse(s: String, acc: List[Char], buffer: String): String = s match { 
    case "" => 
     val accUpdated = purgeBuffer(buffer, acc) 
     accUpdated.foldRight("")((str, b) => b + str) 
    case regexHex(chars, suffix) => 
     traverse(suffix, acc, buffer + chars) 
    case _ => 
     val accUpdated = purgeBuffer(buffer, acc) 
     traverse(s.tail, s.head :: accUpdated, "") 
    } 
    traverse(string, Nil, "") 
} 

Répondre

0

Chaque \x?? encode un octet, comme \x22 encode " et \x5C encode \. Mais en UTF-8 certains caractères sont codés en utilisant plusieurs octets, donc vous devez transformer \xC4\xB1 en ı et ainsi de suite.

replaceAllIn est vraiment bien, mais il pourrait manger vos barres obliques. Donc, si vous n'utilisez pas de groupes (comme \1) dans une chaîne remplacée, quoteReplacement est un moyen recommandé pour échapper \ et $ symboles.

/** "22" -> 34, "AA" -> -86 */ 
def hex2byte(hex: String) = Integer.parseInt(hex, 16).toByte 

/** decode strings like \x22 or \xC4\xB1\xC3\xA7 to specified encoding */ 
def decodeHexadecimals(str: String, encoding: String="UTF-8") = 
    new String(str.split("""\\x""").tail.map(hex2byte), encoding) 

/** fix weird strings */ 
def replaceHexadecimals(str: String, encoding: String="UTF-8") = 
    """(\\x[\dA-F]{2})+""".r.replaceAllIn(str, m => 
    util.matching.Regex.quoteReplacement(
     decodeHexadecimals(m.group(0), encoding))) 

P.S. Est-ce que quelqu'un sait la différence entre et scala.util.matching.Regex.quoteReplacement?

+0

Toutes les variantes pour le faire sans regEx? –

0

Le problème est que le codage est vraiment spécifique à python (je pense). Quelque chose comme cela pourrait fonctionner:

val s = """{\x22value\x22:\x22\xC4\xB1arines Bint\xC4\xB1\xC3\xA7 Ramu\xC3\xA7lar\x22}""" 

"""\\x([A-F0-9]{2})""".r.replaceAllIn(s, (x: Regex.Match) => 
    new String(BigInt(x.group(1), 16).toByteArray, "UTF-8") 
) 
+0

Merci pour la réponse! Cela ne semble pas fonctionner cependant. Le problème est que certains caractères sont codés avec un seul caractère hexadécimal et d'autres sont codés avec une combinaison de deux. J'ai posté une mise à jour de l'article original, également basé sur une expression régulière. –

+0

@HukoJack un chiffre hexadécimal est invalide, il devrait toujours y avoir exactement deux. Sinon, si vous avez '\ xAA', comment diriez-vous si c'est' \ u013A' ou juste 'ɒ' (' \ u252')? La convention est que si vous exécutez '\ x' suivi de n'importe quoi d'autre que deux chiffres hexadécimaux, vous pouvez soit faire une erreur, soit le prendre littéralement. – Dima

+0

@Dima mais dans les cas où vous avez quelque chose comme '" çtext' qui serait codé comme '\ x22 \ xC3 \ xA7text', cette approche gloutonne ne tenterait-elle pas d'abord de décoder' \ x22 \ xC3' et ensuite '\ xA7' et produire une erreur? –