2011-04-14 26 views
23

我希望能够将长字符串换成固定长度。有没有办法做到这一点Guava我可以用番石榴包装文字到一个给定的宽度吗?

Apache Commons/Lang有方法WordUtils.wrap(String, length)这正是我所需要的。番石榴有一个简单的方法来完成这个?

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


UPDATE:现在有这个问题的赏金。

很显然,这种功能在番石榴外不可用,因此赏金会使用Guava中最简洁(或最完整)和番石榴般的答案。没有库存,除了番石榴允许。

+0

我通常有两个库,并使用一个时,其他没有我想要的确切功能。 – Bozho 2011-04-14 11:00:34

+1

@Bozho我也是,但从长远来看,我只想用番石榴替代commons/lang和commons/io。而这个功能目前是交易停止者之一。 – 2011-04-14 11:02:21

+0

只是好奇;你为什么要做自己的文字布局? – 2011-04-16 20:09:57

回答

10

我们(番石榴),强烈建议您使用ICU4J的BreakIterator类来处理用户查找文本破发点的机制。

+0

好的,谢谢! – 2011-10-09 10:02:31

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. 
+0

昨天做了类似的事情,但我把它扔掉了,它不是特别的“番石榴”。几乎没有评论 - 你可以使Joiners的常量重用,尊重ExistingBreaks可以使用CharMatcher.BREAKING_WHITESPACE。还有效率问题,也许最好从0到宽度的子字符串中找到最后一个空间索引,将0切到该索引并添加行和迭代。 – 2011-04-17 09:54:35

+0

...你也可以使用Joiner.appendTo(StringBuilder,Iterable)和我提到的那些常量Joiner ... – 2011-04-17 09:57:15

+0

@Gabriel谢谢,是的,我通常会使用你建议的常量。但我不明白CharMatcher.BREAKING_WHITESPACE如何帮助我尊重现有的休息时间,它可以匹配空格,制表符,新行和作品。 – 2011-04-17 17:35:50

9

为什么要用番石榴做一些更简单的无番石榴?

事实上,Splitter类允许你做使用fixedLength()方法的硬包装,否则你可以拆分取决于分离charString的字符串。如果您想使用番石榴,您可以依靠Splitter.on(' ').split(string),但您还需要根据maxLength值,将结果替换为'\ n'。

不使用番石榴,你也可以做你想做的。几行代码,无依赖关系。基本上,你可以使用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 + ")"); 
     } 
    } 
} 

所以,这件事是:你真的想用番石榴做到这一点?与此选择相关的好处是什么?

+0

+1为你的努力,但我真的想要一些感觉像番石榴。我希望能够a)对包装策略进行微调,就像我可以使用Joiner,Splitter等一样b)将可重用的配置对象存储为常量,再次像Joiner,Splitter等 – 2011-04-17 17:32:11

+0

谢谢,我明白了。如果你想要一些像Guava一样的东西,你的解决方案非常好。也许这也可以要求在番石榴图书馆的这种改进... – javanna 2011-04-17 17:45:35

+0

不,我的解决方案只是一个提示,可能是非常好的东西。我试图想出更像番石榴的东西:-) – 2011-04-17 17:47:17

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

使用PeekingIterator当然是辉煌的(+1) – 2011-04-22 05:55:04