Могу ли я обернуть текст до заданной ширины с помощью Guava?

#java #guava #word-wrap

#java #guava #перенос слов

Вопрос:

Я хотел бы иметь возможность обернуть длинную строку до фиксированной длины. Есть ли способ сделать это в Guava?

У Apache Commons / Lang есть метод WordUtils.wrap(String, length) , который делает именно то, что мне нужно. Есть ли у Guava простое средство для достижения этого?

Я знаю, что могу выполнить жесткую обертку с помощью Splitter.fixedLength(int) , но я бы хотел мягкую обертку.


ОБНОВЛЕНИЕ: Теперь за этот вопрос назначена награда.

Очевидно, что эта функциональность недоступна в Guava «из коробки», поэтому вознаграждение предоставляется за самый краткий (или наиболее полный) ответ, похожий на Guava, который использует то, что есть в Guava. Никакие библиотеки, кроме Guava, не разрешены.

Комментарии:

1. Обычно у меня есть обе библиотеки, и я использую одну, когда другая не обладает нужной мне функциональностью.

2. @Bozho я тоже, но в долгосрочной перспективе я хочу заменить оба commons / lang и commons / io только на Guava. И эта функциональность в настоящее время является одним из ограничителей сделки.

3. Просто любопытно; почему вы делаете свой собственный макет текста?

4. Существует много разных сценариев, тот, который я имел в виду, когда задавал вопрос, касался форматирования длинных сообщений журнала

5. То, что вы пытаетесь сделать, очень чувствительно к i18n, поэтому я настоятельно рекомендую искать решения в ICU4J.

Ответ №1:

Мы (Guava) настоятельно рекомендуем вам использовать BreakIterator класс ICU4J для обработки механики нахождения точек разрыва в пользовательском тексте.

Ответ №2:

Вот мой собственный ответ для вдохновения:

 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);
    }

}
  

Пример использования 1: (жесткая упаковка в 80 символов)

 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.");
  

Вывод:

 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.
  

Пример использования 2: (мягкое обертывание с или или до 60 символов, сохранение существующих разрывов строк)

 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.");
  

Вывод:

 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.
  

Комментарии:

1. Вчера работал над чем-то подобным, но я выбросил это, это было не очень специфично для «guava». Несколько комментариев — вы можете создавать константы из соединителей, которые вы повторно используете, и для исправления ошибок можно использовать CharMatcher. BREAKING_WHITESPACE. Существует также проблема эффективности, возможно, было бы лучше просто найти последний индекс пробела в substring от 0 до width, сократить 0 до этого индекса и добавить к нему строки и выполнить итерацию.

2. …и вы также можете использовать Joiner.appendTo(StringBuilder, Iterable) с теми постоянными соединителями, о которых я упоминал…

3. @Gabriel спасибо, да, обычно я бы использовал константы, как вы предлагаете. Но я не вижу, как работает CharMatcher. РАЗРЫВ_WHITESPACE помог бы мне соблюдать существующие разрывы, он соответствует пробелам, табуляциям, новым строкам, работам.

4. извините, я неправильно понял. Итак, respectExistingBreaks — это уважение существующих новых строк, верно? Если да, то вы могли бы предпочесть BREAKING_WHITESPACE вместо ПРОБЕЛОВ — в javadocs явно указано, что BREAKING_WHITESPACE являются разделителями слов…

Ответ №3:

зачем использовать guava, чтобы сделать что-то более простое без guava?

Фактически, Splitter класс позволяет выполнять жесткую обертку с помощью fixedLength() метода, в противном случае вы можете разделить строку в зависимости от разделителя char или String . Если вы хотите использовать guava, вы можете положиться на Splitter.on(' ').split(string) , но вам также придется объединить результаты, заменив ‘ ‘ на ‘n’ в зависимости от значения maxLength .

Без использования guava вы также можете делать то, что хотите. Несколько строк кода, без зависимостей. В принципе, вы можете использовать подход commons-lang, упрощающий его. Это мой метод переноса:

 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();
}
  

Да, это очень похоже на оригинальный метод commons-lang, но короче, проще и основано на ваших потребностях, я полагаю. Возможно, это решение также более эффективно, чем ваше, не так ли?

Я протестировал это с вашим текстом, сравнив свой результат с результатом commons-lang. Кажется, это работает:

 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   ")");
        }
    }
}
  

Итак, вопрос в следующем: вы действительно хотите использовать guava для этого? Какие преимущества связаны с этим выбором?

Комментарии:

1. 1 за ваши усилия, но я действительно хочу что-то похожее на Guava. И я хотел бы иметь возможность а) точно настроить стратегию переноса, точно так же, как я могу с Joiner, Splitter и т.д. Б) хранить повторно настраиваемые объекты в виде констант, опять же, как Joiner, Splitter и др.

2. Спасибо, я понимаю. Если вы хотите что-то похожее на Guava, ваше решение очень хорошее. Возможно, это улучшение также можно запросить в библиотеках guava…

3. Нет, мое решение — это только намек на то, что могло бы быть очень хорошим. Я пытаюсь придумать что-то, что еще больше похоже на Guava 🙂

4. Не самый приятный код, но я согласен с высказанной точкой зрения. Вы можете довольно легко добавить всю необходимую гибкость, но AFAIK guava вам здесь мало поможет. Вам не нужно просматривать каждый символ и разбивать текст на слова, а затем соединять их обратно. Вам нужно только найти последний индекс пробела перед каждым количеством символов ширины.

Ответ №4:

Я сделал это ради развлечения, просто чтобы сделать как можно больше в guava. ответ джаванны все же лучше,

 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 amp;amp; 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));
    }
}