我可以用Guava将文本换行到给定的宽度吗?

时间:2011-04-14 10:49:42

标签: java guava textwrapping

我希望能够将长字符串包装到固定长度。有没有办法在Guava中做到这一点?

Apache Commons / Lang的方法WordUtils.wrap(String, length)完全符合我的要求。番石榴有没有简单的方法来实现这个目标?

我知道我可以使用Splitter.fixedLength(int)进行硬包装,但我想要一个软包装。


更新:此问题现在有赏金。

显然,这个功能在Guava开箱即用,所以赏金最简洁(或最完整)和番石榴般的答案,使用番石榴中的内容。除了番石榴之外,没有其他库。

4 个答案:

答案 0 :(得分:10)

我们(Guava)强烈建议您使用ICU4J的BreakIterator类来处理在用户文本中查找断点的机制。

答案 1 :(得分:9)

这是我自己的答案,灵感来源:

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.

答案 2 :(得分:9)

为什么在没有番石榴的情况下使用番石榴做更简单的事情?

实际上,Splitter类允许您使用fixedLength()方法进行硬包装,否则您可以根据分隔符charString拆分字符串。如果你想使用番石榴,你可以依靠Splitter.on(' ').split(string),但你也必须加入结果,用'\ n'代替'\ n'取决于maxLength值。

不使用番石榴,你也可以做你想做的事。几行代码,无依赖。基本上,您可以使用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方法非常相似,但更短,更容易,并且根据您的需求,我猜。也许,这个解决方案也比你的更有效,不是吗?

我已经用你的文字对它进行了测试,将我的结果与公共结果进行了比较。它似乎有效:

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

所以,问题是:你真的想用番石榴做这个吗?与此选择相关的好处是什么?

答案 3 :(得分:8)

我这样做很有趣,只是为了尽可能多地在番石榴中做。 javanna的回答虽然好,但

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