Skip to content

Commit

Permalink
Rewrite to match Enketo code as closely as possible.
Browse files Browse the repository at this point in the history
  • Loading branch information
yanokwa committed Jun 10, 2016
1 parent a49fb0a commit 5241490
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2016 Nafundi
*
* 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 org.odk.collect.android.utilities;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.*;

// http://stackoverflow.com/a/2563382/152938

public class ReplaceCallback
{
public static interface Callback {
/**
* This function is called when a match is made. The string which was matched
* can be obtained via match.group(), and the individual groupings via
* match.group(n).
*/
public String matchFound(MatchResult match);
}

/**
* Replaces with callback, with no limit to the number of replacements.
* Probably what you want most of the time.
*/
public static String replace(String pattern, String subject, Callback callback)
{
return replace(pattern, subject, -1, null, callback);
}

public static String replace(String pattern, String subject, int limit, Callback callback)
{
return replace(pattern, subject, limit, null, callback);
}

/**
* @param regex The regular expression pattern to search on.
* @param subject The string to be replaced.
* @param limit The maximum number of replacements to make. A negative value
* indicates replace all.
* @param count If this is not null, it will be set to the number of
* replacements made.
* @param callback Callback function
*/
public static String replace(String regex, String subject, int limit,
AtomicInteger count, Callback callback)
{
StringBuffer sb = new StringBuffer();
Matcher matcher = Pattern.compile(regex).matcher(subject);
int i;
for(i = 0; (limit < 0 || i < limit) && matcher.find(); i++)
{
String replacement = callback.matchFound(matcher.toMatchResult());
replacement = Matcher.quoteReplacement(replacement); //probably what you want...
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);

if(count != null)
count.set(i);
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,99 +15,75 @@
package org.odk.collect.android.utilities;

import android.text.Html;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.util.regex.MatchResult;

public class TextUtils {
private static final String t = "TextUtils";

private TextUtils() {
// static methods only
}

private static String markdownToHtml(String html) {

// https://gist.github.com/jbroadway/2836900
// we try to be as strict as possible
html = html.replaceAll("(\\*\\*|__)(.*?)(\\*\\*|__)", "<strong>$2</strong>");
html = html.replaceAll("(\\*|_)(.*?)(\\*|_)", "<em>$2</em>");
html = html.replaceAll("\\[([^\\[]+)\\]\\(([^\\)]+)\\)", "<a href=\"$2\">$1</a>");

StringBuffer headerOutput = new StringBuffer();
Matcher headerMatcher = Pattern.compile("(?m)^(#+)(.*)").matcher(html);
while (headerMatcher.find()) {
headerMatcher.appendReplacement(headerOutput, headerMatcher.quoteReplacement(createHeaderReplacement(headerMatcher)));
private static ReplaceCallback.Callback createHeader = new ReplaceCallback.Callback() {
public String matchFound(MatchResult match) {
int level = match.group(1).length();
return "<h" + level + ">" + match.group(2).replaceAll( "#+$", "" ).trim() + "</h" + level + ">";
}
html = headerMatcher.appendTail(headerOutput).toString();
};

StringBuffer paragraphOutput = new StringBuffer();
Matcher paragraphMatcher = Pattern.compile("\\n([^\\n]+)\\n").matcher(html);
while (paragraphMatcher.find()) {
paragraphMatcher.appendReplacement(paragraphOutput, headerMatcher.quoteReplacement(createParagraphReplacement(paragraphMatcher)));
private static ReplaceCallback.Callback createParagraph = new ReplaceCallback.Callback() {
public String matchFound(MatchResult match) {
String trimmed = match.group(1).trim();
if (trimmed.matches("(?i)^<\\/?(h|p|bl)")) {
return match.group(1);
}
return "<p>" + trimmed + "</p>";
}
html = paragraphMatcher.appendTail(paragraphOutput).toString();
};

StringBuffer spanOutput = new StringBuffer();
Matcher spanMatcher = Pattern.compile("((&lt;)|<)span(.*?)((&gt;)|>)(.*?)((&lt;)|<)/span((&gt;)|>)").matcher(html);
while (spanMatcher.find()) {
spanMatcher.appendReplacement(spanOutput, headerMatcher.quoteReplacement(createSpanReplacement(spanMatcher)));
private static ReplaceCallback.Callback createSpan = new ReplaceCallback.Callback() {
public String matchFound(MatchResult match) {
String attributes = sanitizeAttributes(match.group(1));
return "<font" + attributes + ">" + match.group(2).trim() + "</font>";
}
html = spanMatcher.appendTail(spanOutput).toString();

return html;
}

public static String createHeaderReplacement(Matcher matcher) {

int level = matcher.group(1).length();
return "<h" + level + ">" + matcher.group(2).trim() + "</h" + level + ">\n";
}

public static String createParagraphReplacement(Matcher matcher) {

String line = matcher.group(1);
String trimmed = line.trim();
if (trimmed.matches("^<\\/?(h|p)")) {
return "\n" + line + "\n";
}
return "\n<p>" + trimmed + "</p>\n";
}

public static String createSpanReplacement(Matcher matcher) {

String stylesText = matcher.group(3);
stylesText = stylesText.replaceAll("style=[\"'](.*?)[\"']", "$1");

String[] styles = stylesText.trim().split(";");
StringBuffer stylesOutput = new StringBuffer();

for (int i = 0; i < styles.length; i++) {
String[] stylesAttributes = styles[i].trim().split(":");
if (stylesAttributes[0].equals("color")) {
stylesOutput.append(" color=\"" + stylesAttributes[1] + "\"");
// throw away all styles except for color and font-family
private String sanitizeAttributes( String attributes ) {

String stylesText = attributes.replaceAll("style=[\"'](.*?)[\"']", "$1");
String[] styles = stylesText.trim().split(";");
StringBuffer stylesOutput = new StringBuffer();

for (int i = 0; i < styles.length; i++) {
String[] stylesAttributes = styles[i].trim().split(":");
if (stylesAttributes[0].equals("color")) {
stylesOutput.append(" color=\"" + stylesAttributes[1] + "\"");
}
if (stylesAttributes[0].equals("font-family")) {
stylesOutput.append(" face=\"" + stylesAttributes[1] + "\"");
}
}
if (stylesAttributes[0].equals("font-family")) {
stylesOutput.append(" face=\"" + stylesAttributes[1] + "\"");
}
}

return "<font" + stylesOutput + ">" + matcher.group(6).trim() + "</font>";
}

// http://stackoverflow.com/a/10187511/152938
public static CharSequence trimTrailingWhitespace(CharSequence source) {

if(source == null)
return "";

int i = source.length();

// loop back to the first non-whitespace character
while(--i >= 0 && Character.isWhitespace(source.charAt(i))) {
return stylesOutput.toString();
}

return source.subSequence(0, i+1);
};

private static String markdownToHtml(String text) {

// https://github.com/enketo/enketo-transformer/blob/master/src/markdown.js

// span - replaced &lt; and &gt; with <>
text = ReplaceCallback.replace( "(?s)<\\s?span([^\\/\n]*)>((?:(?!<\\/).)+)<\\/\\s?span\\s?>", text, createSpan );
// strong
text = text.replaceAll( "(?s)__(.*?)__", "<strong>$1</strong>" );
text = text.replaceAll( "(?s)\\*\\*(.*?)\\*\\*", "<strong>$1</strong>" );
// emphasis
text = text.replaceAll( "(?s)_([^\\s][^_\n]*)_", "<em>$1</em>" );
text = text.replaceAll( "(?s)\\*([^\\s][^\\*\n]*)\\*", "<em>$1</em>" );
// links
text = text.replaceAll( "(?s)\\[([^\\]]*)\\]\\(([^\\)]+)\\)", "<a href=\"$2\" target=\"_blank\">$1</a>" );
// headers - requires ^ or breaks <font color="#f58a1f">color</font>
text = ReplaceCallback.replace( "(?s)^(#+)([^\n]*)$", text, createHeader );
// paragraphs
text = ReplaceCallback.replace( "(?s)([^\n]+)\n", text, createParagraph );

return text;
}

public static CharSequence textToHtml(String text) {
Expand All @@ -116,7 +92,8 @@ public static CharSequence textToHtml(String text) {
return null;
}

return trimTrailingWhitespace(Html.fromHtml(markdownToHtml(text)));
return Html.fromHtml(markdownToHtml(text));

}

}

0 comments on commit 5241490

Please sign in to comment.