¿Puedo ajustar el texto a un ancho determinado con Guayaba?


Me gustaría poder envolver una cadena larga a una longitud fija. ¿Hay una manera de hacer eso en Guayaba ?

Apache Commons / Lang tiene el método WordUtils.wrap(String, length) que hace exactamente lo que necesito. ¿La guayaba tiene un medio simple para lograr esto?

Sé que puedo hacer un wrap duro usando Splitter.fixedLength(int), pero me gustaría una envoltura suave.


ACTUALIZACIÓN: Ahora hay una recompensa por esta pregunta.

Obviamente esta funcionalidad no es disponible en Guayaba fuera de la caja, por lo que la recompensa va a la respuesta más concisa (o más completa) y similar a la Guayaba que usa lo que hay en Guayaba. No se permiten libs excepto Guayaba.

Author: Sean Patrick Floyd, 2011-04-14

4 answers

Nosotros (Guayaba) le recomendamos encarecidamente que utilice ICU4J BreakIterator clase para manejar la mecánica de encontrar puntos de interrupción en el texto del usuario.

 10
Author: Kevin Bourrillion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-08 14:58:20

Aquí está mi propia respuesta, para la inspiración:

public final class TextWrapper {

    enum Strategy implements WrapStrategy {
        HARD {

            @Override
            public String wrap(final Iterable<String> words, final int width) {
                return Joiner.on('\n')
                             .join(Splitter
                                    .fixedLength(width)
                                    .split(
                                        Joiner.on(' ').join(words)));
            }
        },
        SOFT {
            @Override
            public String wrap(final Iterable<String> words, final int width) {
                final StringBuilder sb = new StringBuilder();
                int lineLength = 0;
                final Iterator<String> iterator = words.iterator();
                if (iterator.hasNext()) {
                    sb.append(iterator.next());
                    lineLength=sb.length();
                    while (iterator.hasNext()) {
                        final String word = iterator.next();
                        if(word.length()+1+lineLength>width) {
                            sb.append('\n');
                            lineLength=0;
                        } else {
                            lineLength++;
                            sb.append(' ');
                        }
                        sb.append(word);
                        lineLength+=word.length();
                    }
                }
                return sb.toString();
            }
        }
    }

    interface WrapStrategy {
        String wrap(Iterable<String> words, int width);
    }

    public static TextWrapper forWidth(final int i) {
        return new TextWrapper(Strategy.SOFT, CharMatcher.WHITESPACE, i);
    }

    private final WrapStrategy  strategy;

    private final CharMatcher   delimiter;

    private final int           width;

    TextWrapper(final WrapStrategy strategy,
                final CharMatcher delimiter, final int width) {
        this.strategy = strategy;
        this.delimiter = delimiter;
        this.width = width;
    }

    public TextWrapper hard(){
        return new TextWrapper(Strategy.HARD, this.delimiter, this.width);
    }
    public TextWrapper respectExistingBreaks() {
        return new TextWrapper(
            this.strategy, CharMatcher.anyOf(" \t"), this.width);
    }

    public String wrap(final String text) {
        return this.strategy.wrap(
            Splitter.on(this.delimiter).split(text), this.width);
    }

}

Uso de la muestra 1: (envoltura dura a 80 caracteres)

TextWrapper.forWidth(80)
        .hard()
        .wrap("Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n" +
            "Maecenas porttitor risus vitae urna hendrerit ac condimentum " +
            "odio tincidunt.\nDonec porttitor felis quis nulla aliquet " +
            "lobortis. Suspendisse mattis sapien ut metus congue tincidunt. " +
            "Quisque gravida, augue sed congue tempor, tortor augue rhoncus " +
            "leo, eget luctus nisl risus id erat. Nunc tempor pretium gravida.");

Salida:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas porttitor risu
s vitae urna hendrerit ac condimentum odio tincidunt. Donec porttitor felis quis
 nulla aliquet lobortis. Suspendisse mattis sapien ut metus congue tincidunt. Qu
isque gravida, augue sed congue tempor, tortor augue rhoncus leo, eget luctus ni
sl risus id erat. Nunc tempor pretium gravida.

Uso de la muestra 2: (envoltura suave en o o antes de 60 caracteres, mantenga los saltos de línea existentes)

TextWrapper.forWidth(60)
    .respectExistingBreaks()
    .wrap("Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n" +
    "Maecenas porttitor risus vitae urna hendrerit ac condimentum " +
    "odio tincidunt.\nDonec porttitor felis quis nulla aliquet " +
    "lobortis. Suspendisse mattis sapien ut metus congue tincidunt. " +
    "Quisque gravida, augue sed congue tempor, tortor augue rhoncus " +
    "leo, eget luctus nisl risus id erat. Nunc tempor pretium gravida.");

Salida:

Lorem ipsum dolor sit amet, consectetur adipiscing
elit.
Maecenas porttitor risus vitae urna hendrerit ac
condimentum odio tincidunt.
Donec porttitor felis quis nulla
aliquet lobortis. Suspendisse mattis sapien ut metus congue
tincidunt. Quisque gravida, augue sed congue tempor, tortor
augue rhoncus leo, eget luctus nisl risus id erat. Nunc
tempor pretium gravida.
 9
Author: Sean Patrick Floyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-16 20:49:44

¿Por qué usar guayaba para hacer algo más simple sin guayaba?

De hecho, la clase Splitter le permite hacer un wrap duro usando el método fixedLength(), de lo contrario puede dividir una cadena dependiendo de un separador char o String. Si desea utilizar guayaba, puede confiar en Splitter.on(' ').split(string), pero también tiene que unir los resultados reemplazando '' por '\n' dependiendo del valor maxLength.

Sin usar guayaba, también puedes hacer lo que quieras. Unas pocas líneas de código, con sin dependencias. Básicamente, puede usar el enfoque commons-lang, simplificándolo. Este es mi método de envoltura:

public static String wrap(String str, int wrapLength) {
    int offset = 0;
    StringBuilder resultBuilder = new StringBuilder();

    while ((str.length() - offset) > wrapLength) {
        if (str.charAt(offset) == ' ') {
            offset++;
            continue;
        }

        int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset);
        // if the next string with length maxLength doesn't contain ' '
        if (spaceToWrapAt < offset) {
            spaceToWrapAt = str.indexOf(' ', wrapLength + offset);
            // if no more ' '
            if (spaceToWrapAt < 0) {
                break;
            }
        }

        resultBuilder.append(str.substring(offset, spaceToWrapAt));
        resultBuilder.append("\n");
        offset = spaceToWrapAt + 1;
    }

    resultBuilder.append(str.substring(offset));
    return resultBuilder.toString();
}

Sí, es muy similar al método original de commons-lang, pero más corto, más fácil y basado en sus necesidades, supongo. Tal vez, esta solución también es más eficiente que la suya, ¿no?

Lo he probado con tu texto, comparando mi resultado con el resultado de commons-lang. Parece que funciona:

public static void main(String[] args) {

    String string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
            + "Maecenas porttitor risus vitae urna hendrerit ac condimentum "
            + "odio tincidunt.\nDonec porttitor felis quis nulla aliquet "
            + "lobortis. Suspendisse mattis sapien ut metus congue tincidunt. "
            + "Quisque gravida, augue sed congue tempor, tortor augue rhoncus "
            + "leo, eget luctus nisl risus id erat. Nunc tempor pretium gravida.";

    for (int maxLength = 2; maxLength < string.length(); maxLength++) {
        String expectedResult = WordUtils.wrap(string, maxLength);
        String actualResult = wrap(string, maxLength);

        if (!expectedResult.equals(actualResult)) {
            System.out.println("expectedResult: \n" + expectedResult);
            System.out.println("\nactualResult: \n" + actualResult);
            throw new RuntimeException(
                    "actualResult is not the same as expectedResult (maxLength:"
                            + maxLength + ")");
        }
    }
}

Entonces, el asunto es: ¿realmente quieres usar guayaba para hacer esto? ¿Cuáles son los beneficios relacionados con esta elección?

 9
Author: javanna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-19 11:09:20

Hice esto por diversión solo para hacer tanto en guayaba como sea posible. la respuesta de Javanna es mejor,

import java.util.Iterator;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;


public class SoftSplit {

    public static String softSplit(String string, int length) {
        //break up into words
        Iterable<String> words = Splitter.on(' ').split(string);

        //an iterator that will return the words with appropriate
        //white space added
        final SoftSplitIterator softIter = new SoftSplitIterator(words, length);
        return Joiner.on("").join(new Iterable<String>() {
            @Override
            public Iterator<String> iterator() {
                return softIter;
            }
        });
    }

    static class SoftSplitIterator implements Iterator<String> {
        private final int maxLength;
        private final PeekingIterator<String> words;
        private int currentLineLength;

        SoftSplitIterator(Iterable<String> words, int maxLength) {
            this.words = Iterators.peekingIterator(words.iterator());
            this.maxLength = maxLength;
        }

        @Override
        public boolean hasNext() {
            return words.hasNext();
        }

        @Override
        public String next() {
            String current = words.next();

            //strip leading spaces at the start of a line
            if(current.length() == 0 && currentLineLength == 0) {
                return "";
            }
            //nothing left after us
            if(!words.hasNext()) {
                return current;
            }
            String next = words.peek();

            if(currentLineLength + current.length() + next.length() < maxLength) {
                //this word and the next one won't put us over limit
                currentLineLength += current.length();
                return current + " ";
            } else {
                //the next word will put us over the limit 
                //add a line break
                currentLineLength = 0;
                return current + "\n";
            }
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    public static void main(String[] args) {
        String text = 
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
            "Maecenas porttitor risus vitae urna hendrerit ac condimentum " +
            "odio tincidunt. Donec porttitor felis quis nulla aliquet " +
            "lobortis. Suspendisse mattis sapien ut metus congue tincidunt. " +
            "Quisque gravida, augue sed congue tempor, tortor augue rhoncus " +
            "leo, eget luctus nisl risus id erat. Nunc tempor pretium gravida.";
        System.out.println(softSplit(text, 60));
    }
}
 8
Author: sbridges,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-22 05:52:13