From d042ff67069382970086a7f6dcb52498815b219d Mon Sep 17 00:00:00 2001 From: kohsuke Date: Fri, 5 Jan 2007 07:15:46 +0000 Subject: [PATCH] added an utility class to be used by extensions that mark up cvs change logs. git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@1646 71c3de6d-444a-0410-be80-ed276b4c234a --- core/src/main/java/hudson/MarkupText.java | 239 ++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 core/src/main/java/hudson/MarkupText.java diff --git a/core/src/main/java/hudson/MarkupText.java b/core/src/main/java/hudson/MarkupText.java new file mode 100644 index 0000000000..6c7b8e257c --- /dev/null +++ b/core/src/main/java/hudson/MarkupText.java @@ -0,0 +1,239 @@ +package hudson; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Mutable representation of string with HTML mark up. + * + *

+ * This class is used to put mark up on plain text. + * + * @author Kohsuke Kawaguchi + * @since 1.70 + */ +public class MarkupText { + private final String text; + + /** + * Added mark up tags. + */ + private final List tags = new ArrayList(); + + /** + * Represents one mark up inserted into text. + */ + private static final class Tag implements Comparable { + private final int pos; + private final String markup; + + public Tag(int pos, String markup) { + this.pos = pos; + this.markup = markup; + } + + public int compareTo(Tag that) { + return this.pos-that.pos; + } + } + + /** + * Represents a substring of a {@link MarkupText}. + */ + public final class SubText { + private final int start,end; + private final int[] groups; + + public SubText(Matcher m) { + start = m.start(); + end = m.end(); + + int cnt = m.groupCount(); + groups = new int[cnt*2]; + for( int i=0; i + * Start/end tag text can contain special tokens "$0", "$1", ... + * and they will be replaced by their {@link #group(int) group match}. + * "\\c" can be used to escape characters. + */ + public void surroundWith(String startTag, String endTag) { + addMarkup(start,end,replace(startTag),replace(endTag)); + } + + /** + * Gets the start index of the captured group within {@link MarkupText#getText()}. + * + * @param groupIndex + * 0 means the start of the whole subtext. 1, 2, ... are + * groups captured by '(...)' in the regexp. + */ + public int start(int groupIndex) { + if(groupIndex==0) return start; + return groups[groupIndex*2-2]; + } + + /** + * Gets the start index of this subtext within {@link MarkupText#getText()}. + */ + public int start() { + return start; + } + + /** + * Gets the end index of the captured group within {@link MarkupText#getText()}. + */ + public int end(int groupIndex) { + if(groupIndex==0) return end; + return groups[groupIndex*2-1]; + } + + /** + * Gets the end index of this subtext within {@link MarkupText#getText()}. + */ + public int end() { + return end; + } + + /** + * Gets the text that represents the captured group. + */ + public String group(int groupIndex) { + if(start(groupIndex)==-1) + return null; + return text.substring(start(groupIndex),end(groupIndex)); + } + + /** + * Replaces the group tokens like "$0", "$1", and etc with their actual matches. + */ + private String replace(String s) { + StringBuffer buf = new StringBuffer(); + + for( int i=0; i + * For example, if the text was "abc", then addMarkup(1,2,"<b>","</b>") + * would generate "a<b>b</b>c" + */ + public void addMarkup( int startPos, int endPos, String startTag, String endTag ) { + rangeCheck(startPos); + rangeCheck(endPos); + if(startPos>endPos) throw new IndexOutOfBoundsException(); + + // when multiple tags are added to the same range, we want them to show up like + // abc, not abc. Do this by inserting them to different + // places. + tags.add(0,new Tag(startPos, startTag)); + tags.add(new Tag(endPos,endTag)); + } + + private void rangeCheck(int pos) { + if(pos<0 || pos>text.length()) + throw new IndexOutOfBoundsException(); + } + + /** + * Returns the fully marked-up text. + */ + public String toString() { + if(tags.isEmpty()) + return text; // the most common case + + // somewhat inefficient implementation, if there are a lot of mark up and text is large. + Collections.sort(tags); + StringBuilder buf = new StringBuilder(); + buf.append(text); + int offset = 0; // remember the # of chars inserted. + for (Tag tag : tags) { + buf.insert(tag.pos+offset,tag.markup); + offset += tag.markup.length(); + } + + return buf.toString(); + } + + /** + * Find all "tokens" that match the given pattern in this text. + * + *

+ * A token is like a substring, except that it's aware of word boundaries. + * For example, while "bc" is a string of "abc", calling {@code findTokens} + * with "bc" as a pattern on string "abc" won't match anything. + * + *

+ * This method is convenient for finding keywords that follow a certain syntax + * from natural text. You can then use {@link SubText#surroundWith(String,String)} + * to put mark up around such text. + */ + public List findTokens(Pattern pattern) { + Matcher m = pattern.matcher(text); + List r = new ArrayList(); + + while(m.find()) { + int idx = m.start(); + if(idx>0) { + char ch = text.charAt(idx-1); + if(Character.isLetter(ch) || Character.isDigit(ch)) + continue; // not at a word boundary + } + idx = m.end(); + if(idx