Add CssInlineStyleParser
This commit is contained in:
parent
23b95e70b9
commit
f1a38fca0f
@ -78,6 +78,7 @@ ext {
|
|||||||
|
|
||||||
deps['test'] = [
|
deps['test'] = [
|
||||||
'junit' : 'junit:junit:4.12',
|
'junit' : 'junit:junit:4.12',
|
||||||
'robolectric': 'org.robolectric:robolectric:3.8'
|
'robolectric': 'org.robolectric:robolectric:3.8',
|
||||||
|
'ix-java' : 'com.github.akarnokd:ixjava:1.0.0'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,12 @@ dependencies {
|
|||||||
api it['commonmark-strikethrough']
|
api it['commonmark-strikethrough']
|
||||||
api it['commonmark-table']
|
api it['commonmark-table']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deps['test'].with {
|
||||||
|
testImplementation it['junit']
|
||||||
|
testImplementation it['robolectric']
|
||||||
|
testImplementation it['ix-java']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
|
@ -0,0 +1,171 @@
|
|||||||
|
package ru.noties.markwon.renderer.html2;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
public abstract class CssInlineStyleParser {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public abstract Iterable<CssProperty> parse(@NonNull String inlineStyle);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static CssInlineStyleParser create() {
|
||||||
|
return new Impl();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Impl extends CssInlineStyleParser {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Iterable<CssProperty> parse(@NonNull String inlineStyle) {
|
||||||
|
return new CssIterable(inlineStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CssIterable implements Iterable<CssProperty> {
|
||||||
|
|
||||||
|
private final String input;
|
||||||
|
|
||||||
|
CssIterable(@NonNull String input) {
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Iterator<CssProperty> iterator() {
|
||||||
|
return new CssIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CssIterator implements Iterator<CssProperty> {
|
||||||
|
|
||||||
|
private final CssProperty cssProperty = new CssProperty();
|
||||||
|
|
||||||
|
private final StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
private final int length = input.length();
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
|
||||||
|
prepareNext();
|
||||||
|
|
||||||
|
return hasNextPrepared();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CssProperty next() {
|
||||||
|
if (!hasNextPrepared()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
return cssProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareNext() {
|
||||||
|
|
||||||
|
// clear first
|
||||||
|
cssProperty.set("", "");
|
||||||
|
|
||||||
|
builder.setLength(0);
|
||||||
|
|
||||||
|
String key = null;
|
||||||
|
String value = null;
|
||||||
|
|
||||||
|
char c;
|
||||||
|
|
||||||
|
boolean keyHasWhiteSpace = false;
|
||||||
|
|
||||||
|
for (int i = index; i < length; i++) {
|
||||||
|
|
||||||
|
c = input.charAt(i);
|
||||||
|
|
||||||
|
// if we are building KEY, then when we encounter WS (white-space) we finish
|
||||||
|
// KEY and wait for the ':', if we do not find it and we find EOF or ';'
|
||||||
|
// we start creating KEY again after the ';'
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
|
||||||
|
if (':' == c) {
|
||||||
|
|
||||||
|
// we have no key yet, but we might have started creating it already
|
||||||
|
if (builder.length() > 0) {
|
||||||
|
key = builder.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setLength(0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// if by any chance we have here the ';' -> reset key and try to match next
|
||||||
|
if (';' == c) {
|
||||||
|
builder.setLength(0);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// key cannot have WS gaps (but leading and trailing are OK)
|
||||||
|
if (Character.isWhitespace(c)) {
|
||||||
|
if (builder.length() > 0) {
|
||||||
|
keyHasWhiteSpace = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if not a WS and we have found WS before, start a-new
|
||||||
|
// else append
|
||||||
|
if (keyHasWhiteSpace) {
|
||||||
|
// start new filling
|
||||||
|
builder.setLength(0);
|
||||||
|
builder.append(c);
|
||||||
|
// clear this flag
|
||||||
|
keyHasWhiteSpace = false;
|
||||||
|
} else {
|
||||||
|
builder.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (value == null) {
|
||||||
|
|
||||||
|
if (Character.isWhitespace(c)) {
|
||||||
|
if (builder.length() > 0) {
|
||||||
|
builder.append(c);
|
||||||
|
}
|
||||||
|
} else if (';' == c) {
|
||||||
|
|
||||||
|
value = builder.toString().trim();
|
||||||
|
builder.setLength(0);
|
||||||
|
|
||||||
|
// check if we have valid values -> if yes -> return it
|
||||||
|
if (hasValues(key, value)) {
|
||||||
|
index = i + 1;
|
||||||
|
cssProperty.set(key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
builder.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we must additionally check for EOF (we might be tracking value here)
|
||||||
|
if (key != null
|
||||||
|
&& builder.length() > 0) {
|
||||||
|
value = builder.toString().trim();
|
||||||
|
cssProperty.set(key, value);
|
||||||
|
index = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasNextPrepared() {
|
||||||
|
return hasValues(cssProperty.key(), cssProperty.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasValues(@Nullable String key, @Nullable String value) {
|
||||||
|
return !TextUtils.isEmpty(key)
|
||||||
|
&& !TextUtils.isEmpty(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package ru.noties.markwon.renderer.html2;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public class CssProperty {
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
CssProperty() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(@NonNull String key, @NonNull String value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String key() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public CssProperty mutate() {
|
||||||
|
final CssProperty cssProperty = new CssProperty();
|
||||||
|
cssProperty.set(this.key, this.value);
|
||||||
|
return cssProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CssProperty{" +
|
||||||
|
"key='" + key + '\'' +
|
||||||
|
", value='" + value + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
package ru.noties.markwon.renderer.html2;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ix.Ix;
|
||||||
|
import ix.IxFunction;
|
||||||
|
import ru.noties.markwon.test.TestUtils;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static ru.noties.markwon.test.TestUtils.with;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(manifest = Config.NONE)
|
||||||
|
public class CssInlineStyleParserTest {
|
||||||
|
|
||||||
|
private CssInlineStyleParser.Impl impl;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
impl = new CssInlineStyleParser.Impl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simple_single_pair() {
|
||||||
|
|
||||||
|
final String input = "key: value;";
|
||||||
|
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
|
||||||
|
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key", cssProperty.key());
|
||||||
|
assertEquals("value", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simple_two_pairs() {
|
||||||
|
|
||||||
|
final String input = "key1: value1; key2: value2;";
|
||||||
|
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
|
||||||
|
assertEquals(2, list.size());
|
||||||
|
|
||||||
|
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key1", cssProperty.key());
|
||||||
|
assertEquals("value1", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key2", cssProperty.key());
|
||||||
|
assertEquals("value2", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void one_pair_eof() {
|
||||||
|
|
||||||
|
final String input = "key: value";
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
|
||||||
|
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key", cssProperty.key());
|
||||||
|
assertEquals("value", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void one_pair_eof_whitespaces() {
|
||||||
|
|
||||||
|
final String input = "key: value \n\n\t";
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
|
||||||
|
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key", cssProperty.key());
|
||||||
|
assertEquals("value", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void white_spaces() {
|
||||||
|
|
||||||
|
final String input = "\n\n\n\t \t key1 \n\n\n\t : \n\n\n\n \t value1 \n\n\n\n ; \n key2\n : \n value2 \n ; ";
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
assertEquals(2, list.size());
|
||||||
|
|
||||||
|
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key1", cssProperty.key());
|
||||||
|
assertEquals("value1", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key2", cssProperty.key());
|
||||||
|
assertEquals("value2", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void list_of_keys() {
|
||||||
|
|
||||||
|
final String input = "key1 key2 key3 key4";
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
|
||||||
|
assertEquals(0, list.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void list_of_keys_and_value() {
|
||||||
|
|
||||||
|
final String input = "key1 key2 key3 key4: value4";
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
|
||||||
|
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key4", cssProperty.key());
|
||||||
|
assertEquals("value4", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void list_of_keys_separated_by_semi_colon() {
|
||||||
|
|
||||||
|
final String input = "key1;key2;key3;key4;";
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
assertEquals(0, list.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void key_value_with_invalid_between() {
|
||||||
|
|
||||||
|
final String input = "key1: value1; key2 key3: value3;";
|
||||||
|
final List<CssProperty> list = listProperties(input);
|
||||||
|
|
||||||
|
assertEquals(2, list.size());
|
||||||
|
|
||||||
|
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key1", cssProperty.key());
|
||||||
|
assertEquals("value1", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CssProperty cssProperty) {
|
||||||
|
assertEquals("key3", cssProperty.key());
|
||||||
|
assertEquals("value3", cssProperty.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void css_functions() {
|
||||||
|
|
||||||
|
final Map<String, String> map = new HashMap<String, String>() {{
|
||||||
|
put("attr", "\" (\" attr(href) \")\"");
|
||||||
|
put("calc", "calc(100% - 100px)");
|
||||||
|
put("cubic-bezier", "cubic-bezier(0.1, 0.7, 1.0, 0.1)");
|
||||||
|
put("hsl", "hsl(120,100%,50%)");
|
||||||
|
put("hsla", "hsla(120,100%,50%,0.3)");
|
||||||
|
put("linear-gradient", "linear-gradient(red, yellow, blue)");
|
||||||
|
put("radial-gradient", "radial-gradient(red, green, blue)");
|
||||||
|
put("repeating-linear-gradient", "repeating-linear-gradient(red, yellow 10%, green 20%)");
|
||||||
|
put("repeating-radial-gradient", "repeating-radial-gradient(red, yellow 10%, green 15%)");
|
||||||
|
put("rgb", "rgb(255,0,0)");
|
||||||
|
put("rgba", "rgba(255,0,0,0.3)");
|
||||||
|
put("var", "var(--some-variable)");
|
||||||
|
put("url", "url(\"url.gif\")");
|
||||||
|
}};
|
||||||
|
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
for (Map.Entry<String, String> entry: map.entrySet()) {
|
||||||
|
builder.append(entry.getKey())
|
||||||
|
.append(':')
|
||||||
|
.append(entry.getValue())
|
||||||
|
.append(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CssProperty cssProperty: impl.parse(builder.toString())) {
|
||||||
|
final String value = map.remove(cssProperty.key());
|
||||||
|
assertNotNull(cssProperty.key(), value);
|
||||||
|
assertEquals(cssProperty.key(), value, cssProperty.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, map.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private List<CssProperty> listProperties(@NonNull String input) {
|
||||||
|
return Ix.from(impl.parse(input))
|
||||||
|
.map(new IxFunction<CssProperty, CssProperty>() {
|
||||||
|
@Override
|
||||||
|
public CssProperty apply(CssProperty cssProperty) {
|
||||||
|
return cssProperty.mutate();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
17
markwon/src/test/java/ru/noties/markwon/test/TestUtils.java
Normal file
17
markwon/src/test/java/ru/noties/markwon/test/TestUtils.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ru.noties.markwon.test;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public abstract class TestUtils {
|
||||||
|
|
||||||
|
public interface Action<T> {
|
||||||
|
void apply(@NonNull T t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void with(@NonNull T t, @NonNull Action<T> action) {
|
||||||
|
action.apply(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestUtils() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user