| // Copyright (C) 2016 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.server.mail.send; |
| |
| import static com.google.common.base.Strings.isNullOrEmpty; |
| |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.common.Nullable; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class CommentFormatter { |
| public enum BlockType { |
| LIST, |
| PARAGRAPH, |
| PRE_FORMATTED, |
| QUOTE |
| } |
| |
| public static class Block { |
| public BlockType type; |
| public String text; |
| public List<String> items; // For the items of list blocks. |
| public List<Block> quotedBlocks; // For the contents of quote blocks. |
| } |
| |
| /** |
| * Take a string of comment text that was written using the wiki-Like format and emit a list of |
| * blocks that can be rendered to block-level HTML. This method does not escape HTML. |
| * |
| * <p>Adapted from the {@code wikify} method found in: |
| * com.google.gwtexpui.safehtml.client.SafeHtml |
| * |
| * @param source The raw, unescaped comment in the Gerrit wiki-like format. |
| * @return List of block objects, each with unescaped comment content. |
| */ |
| public static ImmutableList<Block> parse(@Nullable String source) { |
| if (isNullOrEmpty(source)) { |
| return ImmutableList.of(); |
| } |
| |
| ImmutableList.Builder<Block> result = ImmutableList.builder(); |
| for (String p : Splitter.on("\n\n").split(source)) { |
| if (isQuote(p)) { |
| result.add(makeQuote(p)); |
| } else if (isPreFormat(p)) { |
| result.add(makePre(p)); |
| } else if (isList(p)) { |
| makeList(p, result); |
| } else if (!p.isEmpty()) { |
| result.add(makeParagraph(p)); |
| } |
| } |
| return result.build(); |
| } |
| |
| /** |
| * Take a block of comment text that contains a list and potentially paragraphs (but does not |
| * contain blank lines), generate appropriate block elements and append them to the output list. |
| * |
| * <p>In simple cases, this will generate a single list block. For example, on the following |
| * input. |
| * |
| * <p>* Item one. * Item two. * item three. |
| * |
| * <p>However, if the list is adjacent to a paragraph, it will need to also generate that |
| * paragraph. Consider the following input. |
| * |
| * <p>A bit of text describing the context of the list: * List item one. * List item two. * Et |
| * cetera. |
| * |
| * <p>In this case, {@code makeList} generates a paragraph block object containing the |
| * non-bullet-prefixed text, followed by a list block. |
| * |
| * <p>Adapted from the {@code wikifyList} method found in: |
| * com.google.gwtexpui.safehtml.client.SafeHtml |
| * |
| * @param p The block containing the list (as well as potential paragraphs). |
| * @param out The list of blocks to append to. |
| */ |
| private static void makeList(String p, ImmutableList.Builder<Block> out) { |
| Block block = null; |
| StringBuilder textBuilder = null; |
| boolean inList = false; |
| boolean inParagraph = false; |
| |
| for (String line : Splitter.on('\n').split(p)) { |
| if (line.startsWith("-") || line.startsWith("*")) { |
| // The next line looks like a list item. If not building a list already, |
| // then create one. Remove the list item marker (* or -) from the line. |
| if (!inList) { |
| if (inParagraph) { |
| // Add the finished paragraph block to the result. |
| inParagraph = false; |
| block.text = textBuilder.toString(); |
| out.add(block); |
| } |
| |
| inList = true; |
| block = new Block(); |
| block.type = BlockType.LIST; |
| block.items = new ArrayList<>(); |
| } |
| line = line.substring(1).trim(); |
| |
| } else if (!inList) { |
| // Otherwise, if a list has not yet been started, but the next line does |
| // not look like a list item, then add the line to a paragraph block. If |
| // a paragraph block has not yet been started, then create one. |
| if (!inParagraph) { |
| inParagraph = true; |
| block = new Block(); |
| block.type = BlockType.PARAGRAPH; |
| textBuilder = new StringBuilder(); |
| } else { |
| textBuilder.append(" "); |
| } |
| textBuilder.append(line); |
| continue; |
| } |
| |
| block.items.add(line); |
| } |
| |
| if (block != null) { |
| out.add(block); |
| } |
| } |
| |
| private static Block makeQuote(String p) { |
| String quote = p.replaceAll("\n\\s?>\\s?", "\n"); |
| if (quote.startsWith("> ")) { |
| quote = quote.substring(2); |
| } else if (quote.startsWith(" > ")) { |
| quote = quote.substring(3); |
| } |
| |
| Block block = new Block(); |
| block.type = BlockType.QUOTE; |
| block.quotedBlocks = CommentFormatter.parse(quote); |
| return block; |
| } |
| |
| private static Block makePre(String p) { |
| Block block = new Block(); |
| block.type = BlockType.PRE_FORMATTED; |
| block.text = p; |
| return block; |
| } |
| |
| private static Block makeParagraph(String p) { |
| Block block = new Block(); |
| block.type = BlockType.PARAGRAPH; |
| block.text = p; |
| return block; |
| } |
| |
| private static boolean isQuote(String p) { |
| return p.startsWith("> ") || p.startsWith(" > "); |
| } |
| |
| private static boolean isPreFormat(String p) { |
| return p.startsWith(" ") || p.startsWith("\t") || p.contains("\n ") || p.contains("\n\t"); |
| } |
| |
| private static boolean isList(String p) { |
| return p.startsWith("- ") || p.startsWith("* ") || p.contains("\n- ") || p.contains("\n* "); |
| } |
| } |