2009-09-08 5 views
15

Existe-t-il un moyen facile d'indenter la sortie vers un objet ofstream? J'ai un tableau de caractères C++ qui est terminé par null et inclut des sauts de ligne. J'aimerais sortir ceci dans le flux mais indenter chaque ligne avec deux espaces. Existe-t-il un moyen facile de le faire avec les manipulateurs de flux comme vous pouvez modifier la base pour la sortie entière avec des directives spéciales au flux ou dois-je manuellement traiter le tableau et insérer manuellement les espaces supplémentaires à chaque saut de ligne détecté?Comment facilement indenter la sortie à ofstream?

On dirait que la chaîne :: manipulateur droit() est proche:

http://www.cplusplus.com/reference/iostream/manipulators/right/

Merci.

-William

+0

peut-être il est temps que quelqu'un a écrit une bibliothèque pour cela :) – xtofl

+1

Son déjà disponible. C'est appelé une facette. Il est utilisé pour formater la sortie du flux. Ainsi, l'utilisateur du flux peut simplement sortir ses données comme d'habitude. La facette peut alors effectuer n'importe quel format indépendamment (ainsi le format de sortie peut être changé simplement en changeant la facette que le flux utilise sans altérer le code qui produit la sortie). –

Répondre

21

Ceci est la situation idéale pour utiliser une facette.

Une version personnalisée de la facette codecvt peut être imprimée sur un flux.

Ainsi, votre utilisation ressemblerait à ceci:

int main() 
{ 
    /* Imbue std::cout before it is used */ 
    std::ios::sync_with_stdio(false); 
    std::cout.imbue(std::locale(std::locale::classic(), new IndentFacet())); 

    std::cout << "Line 1\nLine 2\nLine 3\n"; 

    /* You must imbue a file stream before it is opened. */ 
    std::ofstream  data; 
    data.imbue(indentLocale); 
    data.open("PLOP"); 

    data << "Loki\nUses Locale\nTo do something silly\n"; 
} 

La définition de la facette est un peu complexe.
Mais tout le problème est que quelqu'un utilisant la facette n'a pas besoin de savoir quoi que ce soit sur le formatage. Le formatage est appliqué indépendamment de la façon dont le flux est utilisé.

#include <locale> 
#include <algorithm> 
#include <iostream> 
#include <fstream> 

class IndentFacet: public std::codecvt<char,char,std::mbstate_t> 
{ 
    public: 
    explicit IndentFacet(size_t ref = 0): std::codecvt<char,char,std::mbstate_t>(ref) {} 

    typedef std::codecvt_base::result    result; 
    typedef std::codecvt<char,char,std::mbstate_t> parent; 
    typedef parent::intern_type      intern_type; 
    typedef parent::extern_type      extern_type; 
    typedef parent::state_type      state_type; 

    int& state(state_type& s) const   {return *reinterpret_cast<int*>(&s);} 
    protected: 
    virtual result do_out(state_type& tabNeeded, 
         const intern_type* rStart, const intern_type* rEnd, const intern_type*& rNewStart, 
         extern_type*  wStart, extern_type*  wEnd, extern_type*&   wNewStart) const 
    { 
     result res = std::codecvt_base::noconv; 

     for(;(rStart < rEnd) && (wStart < wEnd);++rStart,++wStart) 
     { 
      // 0 indicates that the last character seen was a newline. 
      // thus we will print a tab before it. Ignore it the next 
      // character is also a newline 
      if ((state(tabNeeded) == 0) && (*rStart != '\n')) 
      { 
       res     = std::codecvt_base::ok; 
       state(tabNeeded) = 1; 
       *wStart    = '\t'; 
       ++wStart; 
       if (wStart == wEnd) 
       { 
        res  = std::codecvt_base::partial; 
        break; 
       } 
      } 
      // Copy the next character. 
      *wStart   = *rStart; 

      // If the character copied was a '\n' mark that state 
      if (*rStart == '\n') 
      { 
       state(tabNeeded) = 0; 
      } 
     } 

     if (rStart != rEnd) 
     { 
      res = std::codecvt_base::partial; 
     } 
     rNewStart = rStart; 
     wNewStart = wStart; 

     return res; 
    } 

    // Override so the do_out() virtual function is called. 
    virtual bool do_always_noconv() const throw() 
    { 
     return false; // Sometime we add extra tabs 
    } 

}; 

Voir: Tom's notes below

+0

Quel est le résultat attendu ici? Ne semble pas fonctionner comme annoncé ici: [http://liveworkspace.org/code/T4tCi$0](http://liveworkspace.org/code/T4tCi$0) – sehe

+0

@sehe: std :: cout est drôle. Le imbue() ne fonctionnera sur aucun flux si le flux a été utilisé de toute façon. Certaines implémentations utilisent std :: cout avant main() donc l'imbue peut échouer() dans le code ci-dessus. Mais cela fonctionnera toujours sur le fichier. Vérifiez donc le contenu du fichier PLOP. –

+0

Je pense que je l'ai vu ne pas travailler avec ostringstream aussi. Je vais essayer de vérifier plus tard – sehe

2

Eh bien, ce n'est pas la réponse que je suis à la recherche, mais dans le cas où il n'y a pas de réponse, voici une façon de le faire manuellement:

void 
indentedOutput(ostream &outStream, const char *message, bool &newline) 
{ 
    while (char cur = *message) { 
    if (newline) { 
     outStream << " "; 
     newline = false; 
    } 
    outStream << cur; 
    if (cur == '\n') { 
     newline = true; 
    } 
    ++message; 
    } 
} 
1

Il n'y a pas manière simple, mais beaucoup a été écrit sur le complexe façons d'y parvenir. Read this article pour une bonne explication de le sujet. Here is another article, malheureusement en allemand. Mais its source code devrait vous aider. Par exemple, vous pouvez écrire une fonction qui consigne une structure récursive. Pour chaque niveau de récursivité l'empreinte est augmentée:

std::ostream& operator<<(std::ostream& stream, Parameter* rp) 
{ 
    stream << "Parameter: " << std::endl; 

    // Get current indent 
    int w = format::get_indent(stream); 

    stream << "Name: " << rp->getName(); 
    // ... log other attributes as well 

    if (rp->hasParameters()) 
    { 
     stream << "subparameter (" << rp->getNumParameters() << "):\n"; 

     // Change indent for sub-levels in the hierarchy 
     stream << format::indent(w+4); 

     // write sub parameters   
     stream << rp->getParameters(); 
    } 

    // Now reset indent 
    stream << format::indent(w); 

    return stream; 

} 
2

Une façon d'ajouter cette fonctionnalité serait d'écrire un streambuf de filtrage (par exemple un streambuf qui transmet l'opération IO à un autre streambuf mais manipuler les données transférées) qui ajoutent l'indentation dans le cadre de son opération de filtrage. J'ai donné un exemple d'écriture d'un streambuf here et boost fournit un library pour aider à cela. Si votre cas, le membre overflow() testerait simplement '\ n', puis ajouterait le retrait juste après si nécessaire (exactement ce que vous avez fait dans votre fonction indentedOuput, excepté que newline serait membre du streambuf). Vous pourriez probablement avoir un paramètre pour augmenter ou diminuer la taille de l'indentation (peut-être accessible via un manipulateur, le manipulateur devrait faire un dynamic_cast pour s'assurer que le streambuf associé au flux est du type correct, il y a un mécanisme pour ajouter l'utilisateur données à stream - basic_ios :: xalloc, iword et pword - mais ici nous voulons agir sur le streambuf).

2

J'ai eu un bon succès avec codecvt facette suggestion basée Martin, mais j'avais des problèmes en utilisant sur std :: Cout sur OSX, puisque par défaut, ce flux utilise un streambuf basé sur basic_streambuf qui ignore la facette imbue. La ligne suivante bascule std :: cout et friends pour utiliser un streambuf basé sur basic_filebuf, qui utilisera la facette imbue.

std::ios::sync_with_stdio(false); 

Avec l'effet secondaire associé que les objets de flux standard iostream peuvent fonctionner indépendamment des flux C standard.

Une autre remarque est que cette facette n'a pas de statique std :: locale :: id, ce qui signifie que l'appel de std :: has_facet <IndentFacet> sur les paramètres régionaux a toujours renvoyé la valeur true. L'ajout d'un ID std :: local :: signifie que la facette n'a pas été utilisée, puisque basic_filebuf recherche le modèle de la classe de base.

+0

Merci beaucoup. Je cherchais une solution à cela depuis des lustres. –

0

manipulateur simple des espaces

struct Whitespace 
{ 
    Whitespace(int n) 
     : n(n) 
    { 
    } 
    int n; 
}; 

std::ostream& operator<<(std::ostream& stream, const Whitespace &ws) 
{ 
    for(int i = 0; i < ws.n; i++) 
    { 
     stream << " "; 
    } 
    return stream; 
} 
1

Je généralisé la solution de Loki Astarti travailler avec des niveaux d'indentation arbitraires. La solution a une interface agréable, facile à utiliser, mais la mise en œuvre réelle est un peu louche. Il se trouve sur github: https://github.com/spacemoose/ostream_indenter

Il y a une démo plus impliqué dans le repo github, mais étant donné:

#include "indent_facet.hpp" 

/// This probably has to be called once for every program: 
// http://stackoverflow.com/questions/26387054/how-can-i-use-stdimbue-to-set-the-locale-for-stdwcout 
std::ios_base::sync_with_stdio(false); 

// This is the demo code: 
std::cout << "I want to push indentation levels:\n" << indent_manip::push 
      << "To arbitrary depths\n" << indent_manip::push 
      << "and pop them\n" << indent_manip::pop 
      << "back down\n" << indent_manip::pop 
      << "like this.\n" << indent_manip::pop; 

}

Il produit la sortie suivante:

I want to push indentation levels: 
    To arbitrary depths 
     and pop them 
    back down 
like this. 

J'apprécierais toute rétroaction quant à l'utilité du code.

+0

Bien que ce soit une très bonne idée d'utiliser codecvt pour l'autoindenting, il y a quelques problèmes avec votre code source: 1) Lorsque vous imprimez des tabulations dans une boucle, vous ne pouvez pas vérifier le débordement de buffer; (à suivre dans le commentaire suivant) – segfault

+0

2) Après avoir corrigé le premier problème, il y a un autre problème avec l'implémentation libstdC++ de codecvt, qui n'appelle 'do_out' qu'une seule fois si le résultat de la dernière opération était' std :: codecvt_base :: partial ', donc si la ligne n'a que peu de caractères (par exemple, accolade ouvrante) et que le niveau d'indentation est grand, certains caractères seront perdus. Je ne sais pas comment résoudre cela correctement, la seule solution laide que je connais est de surcharger 'do_max_length()' pour retourner une grande valeur forçant libstdC++ à allouer un tampon de sortie assez grand. – segfault

Questions connexes