[Swift]Conversion NSAttributedString <=> NSData
Il est simple de fabriquer un NSData à partir d'un String.
let str = "Sushi"
let dataSushi = str.dataUsingEncoding(NSUTF8StringEncoding)
Et dans le sens inverse (NSData => String)
let strData = String(data: dataSushi!, encoding: NSUTF8StringEncoding)
Je n'arrive pas à faire la même chose avec un NSAttributedString. D'après la documentation, il faut utiliser la méthode dataFromRange, avec un paramètre documentAttributes.
let miam = NSAttributedString(string: "Miam miam")
let dataMian = miam.dataFromRange(range: NSRange, documentAttributes: [String : AnyObject])
Si j'essaye de passer [:], je me fait jeter par Xcode.
let miam = NSAttributedString(string: "Miam miam")
let dataMiam = miam.dataFromRange(range: NSRange, documentAttributes: [:])
Il m'explique que "Call can throw, but it not marked with 'try' and the error is not handled." J'ai survolé le chapitre sur le Error handling pour trouver la bonne syntaxe, mais il est 2 heures du matin et mon esprit patine dans la gadoue)
Une suggestion pour la conversion NSAttributedString => NSData et vice-versa ?
J'arrive à le faire en utilisant le protocole NSCoding, mais le résultat est trop gourmand en mémoire, 540 octets pour un simple "Sushi". Le seul point positif est qu'il n'y a pas besoin des paramètres abscons de NSAttributedString.dataFromRange().
class UnTexte : NSObject, NSCoding {
var texte = NSAttributedString()
init(texte: NSAttributedString) {
self.texte = texte
}
required init?(coder aDecoder: NSCoder) {
self.texte = aDecoder.decodeObjectForKey("texte") as! NSAttributedString
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.texte, forKey: "texte")
}
}
let attStr = NSAttributedString(string: "Sushi")
let texte = UnTexte(texte: attStr)
let dataTexte = NSKeyedArchiver.archivedDataWithRootObject(texte)
print(dataTexte.length)
Je présume que le mécanisme du NSCoding prend de la place pour ces besoins internes, mais 540 octets pour un simple NSAttributedString de 5 caractères, c'est beaucoup ..
Par curiosité, j'ai fait le même test avec NSCoding et un String de base, pour arriver à 302 octets avec un simple "Sushi". C'est encore trop, alors que cela ne prend que 5 octets avec String.dataUsingEncoding.
Réponses
Il te faut les attributs aussi de ton NSAttributedString ou juste le String ?
Parce que dans ce cas là tu as la propriété string de toute instance de NSAttributedString et qui renvoie le texte brut.
Oui il faut lire la gestion des erreurs . et il faut que tu passes aussi au moins un attributs
let dataMian = try? miam.dataFromRange(range, documentAttributes: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType])
Un joli texte prend beaucoup de son intérêt sans le gras et l'italique.
Merci, ça marche. Mais c'est gourmand ..
550 octets pour stocker "miam miam".
En utilisant NSRTFTextDocument pour documentAttributes ça tombe à 250 octets.
Et 9 octets avec NSPlainTextDocument ! Prochaine étape, tester avec des enrichissements dans le texte.
Pas sûr que NSPlainTextDocument garde les attributs...
Je me demande à quel point est optimisée la conversion inverse, que donne let strData = String(data: dataMiam!, encoding: NSUTF8StringEncoding), en fonction des différentes transformations possibles ?
C'est ce que je me disais aussi. Je verrais en testant. Mais il reste toujours le format RTF.
C'est fou de faire toutes ces manipulations juste pour conserver le gras et les italiques dans la chaine stockée sur disque.
Ce que je cherche à faire, c'est stocker une String dans un NSData dans le format le plus compact possible, avec des indicateurs de gras et d'italique. Je n'ai pas besoin de toute la puissance des NSAttributedString, juste de conserver gras et italique.
Si tu reçois les strings avec les balises toi-même, il est peut-être plus intéressant de les garder avec les balises... Potentiellement, la version sans balise aussi (pour faire une recherche sur le string).
A l'origine le texte est un NSAttributedString extrait d'un fichier RTF.
Je n'ai plus le temps de continuer maintenant, mais j'ai vu que la taille du NSData au format de document RTF n'augmente pas très vite. Il semble y avoir un entête de 240 octets, ensuite chaque caractère ne prend qu'un octet (du moins les caractères classique de l'alphabet). Ce n'est pas une perte très importante, avec des textes longs.
Après vérification, il ne les garde pas. >:(
J'ai fait ce test :
​
Quand on logs les attributedstrings, on voit que beaucoup d'informations sont sauvegardées.
ça doit être pareil avec le RTF, j'ai pas testé.
D'après mes essais, le format RTF est environ deux fois moins gourmand que le HTML, fort heureusement. Mais cela reste encore par rapport au texte "pur".
J'ai essayé avec ce texte :
"Il ne faut pas confondre les sushis avec les sashimis, un plat japonais constitué de tranches de poisson cru."
Encapsulé dans un NSData, il prend 110 octets.
Stocké dans un NSAttributed, puis encapsulé dans un NSData au format RTF : 353 octets
La même chose au format NSData HTLM : 651 octets
La taille du NSData est identique si j'utilise un NSMutableAttributedString.
C'est du texte brut, sans le moindre attribut. Ou alors juste les attributs que iOS lui assigne pendant la création du NSAttributedString.
J'ai ajouté un attribut de couleur sur tous les caractères, cela fait passer le NSData (format RTF) à 374 octets, soit une différence de +21 octets. C'est intéressant parce qu'il y a justement 21 blocs de lettres dans le texte, en comptant la virgule et le point final.
Avec le même attribut de couleur, le NSData (format HTLM) passe de 651 octets à 667 octets (+16 octets).
Une surprise : J'ai ensuite essayé de changer la police de caractères du texte. En NSData RTF cela fait baisser la taille de la chaà®ne. De 353 octets, ça passe à 349 octets ! 4 octets, c'est toujours ça de gagné ..
Le phénomène ne se reproduit pas en HTML. Le changement de police ajoute 16 octets à la taille, exactement comme pour la couleur.
Je résume :
String : 110 octets
NSData format RTF : 353 octets
NSData format HTML : 651 octets
Conclusion le format RTF c'est bien, le format HTLM c'est mal. Du moins pour stocker des textes enrichis dans un NSData.
Texte|attribut1|Index debut|Index fin|attribut2|debut|fin ...
En utilisant la fonction enumerateAttributesInRange pour parcourir les attributs.
Il y a aussi le format BBCode et je ne serais pas étonné que tu trouves des sources qui font la conversion vers BBCode sur github.
C'est exactement ce que j'étais en train de me dire !
Je comprend mieux l'excédent de poids d'un NSAttributedString après le stockage dans un NSData. iOS ajoute des tas d'informations de présentation au passage.
Exemple :
Affichage de attStr dans la console :
Conversion en NSData (format RTF) et génération d'un nouvel NSAttributedString :
Affichage de texteFinal dans la console :
C'est copieux ..
Encore pire avec un NSData au format HTML :
La même chose au format de document Plain :
Beaucoup plus raisonnable sur l'occupation mémoire, mais si triste à l'affichage !
Cela permet d'avoir un texte qui aura la même apparence partout. A opposer à une autre méthode qui constisterait à n'inclure aucune valeur par défaut et à laisser chaque programme consommateur du texte appliquer ses valeurs par défaut.
Je ne crois pas qu'il y ait de bonne solution à ce problème.
Cependant, il y a quand même un soucis, c'est que la valeur par défaut de la police est "Helvetica" alors qu'on est passé en "San Francsisco" sur tous les systèmes Apple. Tu pourrais soumettre un radar pour ça.
Quant à la taille finale, ils pourraient éviter de mettre des valeurs par défaut quand ces valeurs sont zéro et que zéro a un sens pour la valeur (HeadIdent 0, Tailleident0, etc.) Je pense que c'est une juste un raccourci du développeur qui écrit toujours le contenu de sa structure interne en serialisation, que les valeurs ait été positionnées à des valeurs significatives ou non.
En général, en pratique, on s'en fout car les tailles que tu cites sont faibles.
Cela dit sur du Bluetooth low energy, par exemple, cela commence à compter.
Je vais reprendre mon idée initiale :
Modifies-tu le texte ?
Car sinon, tu pourrais juste sauvegarder des strings qui contiennent les balises. Cela sera moins lourd à sauver, mais il y a aura toujours un petit temps à faire le parsing/init à partir de HTML ou autre.
Autre solution, combien "d'effets" différents gères-tu ? S'il y en a très peu, un serializer pourrait être intéressant, tu gardes le texte, et des attributs (isBold, isItalic, etc.à ) sur les ranges adéquates.
Oui enfin d'un autre coÌ‚teÌ Helvetica n'était pas la police par défaut pour les interfaces d'OSX mais bien Lucida Grande.
Helvetica a, par contre, toujours eÌteÌ la police par défaut pour la saisie de texte. On peut donc s'eÌconomiser un radar...
C'est la solution vers laquelle je me tourne en ce moment, un string, et deux tableaux pour garder les ranges des isBold et isItalic. Ce qui permet de stocker la "forme structurelle" du texte et de fabriquer un NSAttributedString à la demande avec différentes polices.
Merci pour la précision, j'avais oublié Lucida Grande.
Mais en fait, pour la police système, je pensais au retour de systemFontOfSize. Cela m'aurait paru logique que la police utilisée soit la même, mais je ne savais pas qu'il y avait une police différente pour l'édition de texte. Du coup comment récupère-t-on cette dernière par API ?
Parce que j'avoue que j'utilisais toujours systemFontOfSize que ce soit pour de l'affichage ou de l'édition.
Non, justement. Ils ne fixent pas les valeurs de formatage par défaut au moment de la création du texte enrichi, uniquement pendant la génération d'un NSData au format de document RTF ou HTML. Je présume que c'est plus une question d'entête de fichier RTF/HTML que du NSAttributedString lui-même.
Quand c'est un NSData au format de document Plain, le système se contente de lui associer une font par défaut, si aucune n'est déjà présente.
Il y a une grosse différence entre la taille sérialisée sur disque (surtout en RTF, qui est un format plutôt dense contrairement à l'HTML) et la taille du texte affichée dans la console quand tu imprimes la debugDescription de ton objet... pas très pertinent comme comparaison.
Et quitte à faire des comparaisons, il faut les faire sur des textes de longueurs variables et variés (par exemple plusieurs tailles de Lorem Ipsums), car par exemple si le format a un prologue / header qui prend 1Ko, bah sur un texte de 200 caractères ça va te sembler énorme tu vas dire que ça fait du x6, alors que sur un texte de 500 mots ça va devenir ridicule.
Bin non, je mesure l'occupation mémoire des NSData avec l'opérateur lenght. Le print console c'est pour voir ce qui peut bien trainer dans le NSAttributedString, avant et après l'encapsulation dans le NSData.
Le format Plain est triste à l'affichage à cause de la perte des attributs d'enrichissement, pas suite à un log console presque vide.
Oui, un petit entête est négligeable sur un texte de 500 mots, mais sur un texte de 50 ou même de 20 mots ? Imagine qu'il s'agisse d'un quiz style Trivial Pursuit avec des milliers de questions courtes.
"En quelle année s'est déroulée la bataille de Marignan ?" (11 mots)
"1286" (1 mot)
"1515" (1 mot)
"1674" (1 mot)
"Où s'est déroulée la bataille de Marignan ?" (9 mots)
"Du coté de Milan" (4 mots)
"A Chartres" (2 mots)
"Sur la frontière espagnole" (4 mots)
"Qui a gagné la bataille de Marignan ?" (8 mots)
"Les Prussiens" (2 mots)
"Les Français" (2 mots)
"Les Mercenaires Suisses" (3 mots)
Si le petit entête négligeable se retrouve avec chaque question/réponse, il vas prendre une place colossale par rapport aux données utiles.
Bah alors si c'est que pour rendre du texte stockeÌ mais qui ne sera pas saisi utilise du markdown...
J'ai justement un petit framework qui traine: sur Github. La fonction commonMarkToHTML te permettra très facilement de transformer tout ça à la voleÌe. Ensuit tu récupère un String HTML que tu peux convertir en NSAttributedString.
ça fait que "En quelle année s'est déroulée la *bataille de Marignan* ?" donnera "En quelle année s'est déroulée la bataille de Marignan ?" ou encore "**Mauvaise** réponse !" s'affichera "Mauvaise reÌponse !'
(D'ailleurs en parlant de Markdown l'eÌditeur de CocoaCafeÌ ne pourrait pas le supporter avec un plugin IP Board ? Oui je sais que c'est du boulot...)
Un mal pour un bien.
NSFont.userFontOfSize renvoie Helvetica 12pt sous OSX 10.11, pas testé sous iOS.
J'y ai pensé, mais le markdown n'est pas à la portée de tous. Si je demande par exemple a un historien de me rédiger 200 questions, il sais se servir d'un traitement de textes, pour taper du texte de manière classique, pas forcément du markdown. Même chose pour un prof d'anglais, de physique, de sports, un photographe, un avocat, un peintre, etc .. Les données doivent être saisies sous une forme naturelle et directement lisible par leur créateurs, pour éviter les erreurs ici et là .
Tu vas me dire "c'est affiché de manière naturelle avec un éditeur markdown". Ce qui n'est pas d'une grande utilité avec un type ne connaissant que son traitement de texte (Word généralement) et qui n'a pas l'intention d'en changer et de prendre de nouvelles habitudes juste pour taper une série de questions.
J'ai eu de très mauvaises expériences à l'époque où je bossais avec Ubi Soft en bossant avec des graphistes. Même en rédigeant un cahier de charges précis, style "Je veux 12 images de 48x48 pixels, représentant tel mouvement de ce personnage. Cela doit utiliser les 16 couleurs suivantes (il y a 25 ans, les contraintes de couleurs étaient terribles avec les cartes graphiques EGA et VGA, sans parler de l'Atari ST ou de l'Amstrad CPC). Les graphismes doivent être placés sur une image sur les positions suivantes : ....." On se retrouvais souvent avec une ou deux images de 48x52 pixels, parce que c'était plus "joli", ou des graphismes positionnés à quelques pixels de l'endroit demandé, ou carrément très loin. Et il fallait reprendre les fichiers graphiques à la main, avec un logiciel de dessin.
@Draken: De toute manière tu vas devoir traiter les données brutes que tu vas recevoir non ? Admettons que tu reçoive un document Word, meÌ‚me si tu peux initialiser une NSAttributedString depuis un document word (AppKit only il me semble) il va quand meme falloir que tu découpe le tout et que tu mette ça dans un modeÌ€le...
À moins que tu ne demande à tes fournisseurs de contenu de te filer directement des plist correctement formatées il va falloir que tu fasse une moulinette. Je suis prêt à t'aider pour cette partie là .
Un fichier word avec juste du texte ça se transforme facilement en RTF.
Il n'est évidemment pas question de demander à des non-informaticiens de fournir des plist ou des fichiers XML avec balises. Un format simple comme celui-ci peut suffire :
En se basant sur les lignes vides pour séparer les questions.
Vous m'avez déjà grandement aidé, toi, Ali et FKDEV pour le découpage d'un RTF en tableau de NSAttributedString, dans ce topic :
http://forum.cocoacafe.fr/topic/13581-probablement-resolu-swift-chargement-dun-fichier-rtf-dans-un-tableau-de-nsattributedstring/?hl=nsattributedstring
Oui je comprends mieux maintenant.
Mais néanmoins je pense qu'on touche quand même à une chose importante ici c'est qu'il y aura toujours plusieurs manières de faire quelque chose (chaque personne ayant posteÌ ici a apporteÌ au moins une méthode). Le vrai challenge est de savoir quelle méthode est la mieux adaptée à ce qu'on veut réellement faire.
Alors je te pose la question: qu'est-ce que tu veux réellement faire ?