2009-09-08 15 views
15

¿Hay alguna manera fácil de sangrar la salida que va a un objeto corriente? Tengo una matriz de caracteres C++ que es nulo terminar e incluye nuevas líneas. Me gustaría enviar esto a la secuencia, pero sangrar cada línea con dos espacios. ¿Hay una manera fácil de hacer esto con los manipuladores de flujo como puede cambiar la base para salida entera con directivas especiales a la secuencia o tengo que procesar manualmente la matriz e insertar los espacios adicionales manualmente en cada salto de línea detectado?¿Cómo doblar fácilmente la salida a ofstream?

parece que el manipulador string :: derecha() está cerca:

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

Gracias.

William

+0

tal vez es hora de que alguien haya escrito una biblioteca para esto :) – xtofl

+1

Ya está disponible. Se llama faceta. Se usa para formatear la salida de la secuencia. Por lo tanto, el usuario de la transmisión puede generar datos allí de forma normal. La faceta puede entonces realizar cualquier formato de forma independiente (por lo tanto, el formato de salida puede cambiarse simplemente cambiando la faceta que utiliza la secuencia sin alterar el código que produce la salida). –

Respuesta

21

Esta es la situación perfecta para usar una faceta.

Una versión personalizada de codecvt facet se puede imbuir en una secuencia.

Por lo que su uso sería el siguiente aspecto:

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 definición de la faceta es ligeramente más complejo.
Pero el punto es que alguien que use la faceta no necesita saber nada sobre el formateo. El formato se aplica independientemente de cómo se usa la secuencia.

#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 
    } 

}; 

Ver: Tom's notes below

+0

¿Cuál es el resultado esperado aquí? No parece funcionar como se anuncia aquí: [http://liveworkspace.org/code/T4tCi$0](http://liveworkspace.org/code/T4tCi$0) – sehe

+0

@sehe: std :: cout es gracioso. El archivo imbue() no funcionará en ninguna secuencia si la secuencia se ha utilizado de todos modos. Algunas implementaciones usan std :: cout before main(), por lo que el imbue puede fallar() en el código anterior. Pero siempre funcionará en el archivo. Así que verifique el contenido del archivo PLOP. –

+0

Creo que también vi que no funciona con ostringstream. Trataré de verificar más tarde – sehe

2

Bueno, esto no es la respuesta que estoy buscando, pero en caso de que no hay tal respuesta, aquí hay una manera de hacer esto manualmente:

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

No hay manera simple, pero mucho se ha escrito sobre las formas complejas de lograr esto. Read this article para una buena explicación de el tema. Here is another article, desafortunadamente en alemán. Pero its source code debería ayudarlo.

Por ejemplo, puede escribir una función que registra una estructura recursiva. Para cada nivel de recursividad se aumenta la indentación:

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

una manera de añadir tal función sería la de escribir un streambuf de filtrado (es decir, un streambuf que reenvía la operación IO a otro streambuf pero manipular los datos transferidos) que se suman la sangría como parte de su operación de filtro. Di un ejemplo de escribir un streambuf here y boost proporciona un library para ayudar en eso.

Si su caso, el componente de retorno() sería simplemente la prueba de '\ n' y luego añadir el guión justo después, si es necesario (exactamente lo que han hecho en su función indentedOuput, exceptuado que newline sería un miembro de la streambuf). Probablemente podría tener una configuración para aumentar o disminuir el tamaño de sangría (tal vez accesible a través de un manipulador, el manipulador tendría que hacer un dynamic_cast para asegurarse de que el streambuf asociado a la secuencia sea del tipo correcto; hay un mecanismo para agregar usuario data to stream - basic_ios :: xalloc, iword y prword - pero aquí queremos actuar en streambuf).

2

He tenido buen éxito con sugerencia basada codecvt faceta de Martin, pero había problemas de usarlo en std :: cout en OSX, ya que por defecto esta corriente utiliza una basic_streambuf basado streambuf que ignora la faceta imbuida. La siguiente línea cambia a std :: cout y amigos para que usen un streambuf basado en archivos básicos, que usará la faceta imbuida.

std::ios::sync_with_stdio(false); 

Con el efecto secundario asociado de que los objetos de flujo estándar de iostream pueden funcionar independientemente de los flujos de C estándar.

Otra nota es desde esta faceta no tiene un std :: estática local :: identificación, lo que significaba que llamar std :: has_facet <IndentFacet> de la configuración regional siempre devuelve verdadero. Agregar un std :: local :: id significaba que la faceta no se usó, ya que basic_filebuf busca la plantilla de la clase base.

+0

Gracias. He estado buscando una solución para esto por años. –

0

manipulador espacios en blanco simple

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

He generalizado solución de Loki Astarti para trabajar con niveles de sangría arbitrarias. La solución tiene una interfaz agradable y fácil de usar, pero la implementación real es un poco sospechosa. Se puede encontrar en GitHub: https://github.com/spacemoose/ostream_indenter

Hay una demostración más involucrados en el repositorio GitHub, pero teniendo en cuenta:

#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; 

}

se produce el siguiente resultado:

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

Agradecería cualquier comentario sobre la utilidad del código.

+0

si bien esta es una gran idea sobre el uso de codecvt para la autoindentación, existen pocos problemas con su código fuente: 1) Al imprimir tabulaciones en un bucle, no se comprueba el desbordamiento del búfer '_to', este se puede resolver fácilmente; (continuará en el próximo comentario) – segfault

+0

2) Después de solucionar el primer problema, hay otro problema con la implementación libstdC++ de codecvt, que llama 'do_out' solo una vez si el resultado de la última operación fue' std :: codecvt_base :: partial ', entonces, si la línea tiene solo unos pocos caracteres (por ejemplo, abrazadera de apertura) y el nivel de sangría es grande, algunos caracteres se perderán. No sé cómo resolver esto correctamente, la única solución fea que sé es anular 'do_max_length()' para devolver un valor grande forzando a libstdC++ a asignar un buffer de salida lo suficientemente grande. – segfault

Cuestiones relacionadas