Add attributes to start tags

This commit is contained in:
Dimitry Ivanov 2018-08-17 15:58:25 +03:00
parent bf8ff03b1c
commit be484765da
5 changed files with 147 additions and 86 deletions

View File

@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.List;
import java.util.Map;
/**
* @see Inline
@ -32,6 +33,9 @@ public interface HtmlTag {
*/
boolean isEmpty();
@NonNull
Map<String, String> attributes();
/**
* Represents <em>really</em> inline HTML tags (unline commonmark definitions)
*/

View File

@ -5,6 +5,7 @@ import android.support.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
abstract class HtmlTagImpl implements HtmlTag {
@ -12,11 +13,13 @@ abstract class HtmlTagImpl implements HtmlTag {
final String name;
final int start;
final Map<String, String> attributes;
int end = NO_VALUE;
protected HtmlTagImpl(@NonNull String name, int start) {
protected HtmlTagImpl(@NonNull String name, int start, @NonNull Map<String, String> attributes) {
this.name = name;
this.start = start;
this.attributes = attributes;
}
@NonNull
@ -40,16 +43,23 @@ abstract class HtmlTagImpl implements HtmlTag {
return start == end;
}
@NonNull
@Override
public Map<String, String> attributes() {
return attributes;
}
boolean isClosed() {
return end > NO_VALUE;
}
abstract void closeAt(int end);
static class InlineImpl extends HtmlTagImpl implements Inline {
InlineImpl(@NonNull String name, int start) {
super(name, start);
InlineImpl(@NonNull String name, int start, @NonNull Map<String, String> attributes) {
super(name, start, attributes);
}
@Override
@ -65,6 +75,7 @@ abstract class HtmlTagImpl implements HtmlTag {
"name='" + name + '\'' +
", start=" + start +
", end=" + end +
", attributes=" + attributes +
'}';
}
}
@ -74,20 +85,28 @@ abstract class HtmlTagImpl implements HtmlTag {
@NonNull
static BlockImpl root() {
//noinspection ConstantConditions
return new BlockImpl("", 0, null);
return new BlockImpl("", 0, null, null);
}
@NonNull
static BlockImpl create(@NonNull String name, int start, @NonNull BlockImpl parent) {
return new BlockImpl(name, start, parent);
static BlockImpl create(
@NonNull String name,
int start,
@NonNull Map<String, String> attributes,
@NonNull BlockImpl parent) {
return new BlockImpl(name, start, attributes, parent);
}
final BlockImpl parent;
List<BlockImpl> children;
@SuppressWarnings("NullableProblems")
BlockImpl(@NonNull String name, int start, @NonNull BlockImpl parent) {
super(name, start);
BlockImpl(
@NonNull String name,
int start,
@NonNull Map<String, String> attributes,
@NonNull BlockImpl parent) {
super(name, start, attributes);
this.parent = parent;
}
@ -127,12 +146,24 @@ abstract class HtmlTagImpl implements HtmlTag {
return (List<Block>) (List<? extends Block>) children;
}
@NonNull
@Override
public Map<String, String> attributes() {
//noinspection ConstantConditions
if (attributes == null) {
throw new IllegalStateException("#attributes() getter was called on the root node " +
"which should not be exposed outside internal usage");
}
return attributes;
}
@Override
public String toString() {
return "BlockImpl{" +
"name='" + name + '\'' +
", start=" + start +
", end=" + end +
", attributes=" + attributes +
", parent=" + (parent != null ? parent.name : null) +
", children=" + children +
'}';

View File

@ -7,14 +7,19 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import ru.noties.markwon.html.HtmlTag.Block;
import ru.noties.markwon.html.HtmlTag.Inline;
import ru.noties.markwon.html.HtmlTagImpl.BlockImpl;
import ru.noties.markwon.html.HtmlTagImpl.InlineImpl;
import ru.noties.markwon.html.jsoup.nodes.Attribute;
import ru.noties.markwon.html.jsoup.nodes.Attributes;
import ru.noties.markwon.html.jsoup.parser.CharacterReader;
import ru.noties.markwon.html.jsoup.parser.ParseErrorList;
import ru.noties.markwon.html.jsoup.parser.Token;
@ -209,7 +214,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
final String name = startTag.normalName;
final InlineImpl inline = new InlineImpl(name, output.length());
final InlineImpl inline = new InlineImpl(name, output.length(), extractAttributes(startTag));
if (isVoidTag(name)
|| startTag.selfClosing) {
@ -271,7 +276,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
final int start = output.length();
final BlockImpl block = BlockImpl.create(name, start, currentBlock);
final BlockImpl block = BlockImpl.create(name, start, extractAttributes(startTag), currentBlock);
final boolean isVoid = isVoidTag(name) || startTag.selfClosing;
if (isVoid) {
@ -396,4 +401,25 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
append(output, "\n");
}
}
@NonNull
protected static Map<String, String> extractAttributes(@NonNull Token.StartTag startTag) {
Map<String, String> map;
final Attributes attributes = startTag.attributes;
final int size = attributes.size();
if (size > 0) {
map = new HashMap<>(size);
for (Attribute attribute : attributes) {
map.put(attribute.getKey().toLowerCase(Locale.US), attribute.getValue());
}
map = Collections.unmodifiableMap(map);
} else {
map = Collections.emptyMap();
}
return map;
}
}

View File

@ -137,13 +137,13 @@ public class Attribute implements Map.Entry<String, String>, Cloneable {
// return new Attribute(unencodedKey, value, null); // parent will get set when Put
// }
protected boolean isDataAttribute() {
return isDataAttribute(key);
}
protected static boolean isDataAttribute(String key) {
return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length();
}
// protected boolean isDataAttribute() {
// return isDataAttribute(key);
// }
//
// protected static boolean isDataAttribute(String key) {
// return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length();
// }
// /**
// * Collapsible if it's a boolean attribute and value is empty or same as name

View File

@ -28,7 +28,7 @@ import static ru.noties.markwon.html.jsoup.helper.Normalizer.lowerCase;
* @author Jonathan Hedley, jonathan@hedley.net
*/
public class Attributes implements Iterable<Attribute>, Cloneable {
protected static final String dataPrefix = "data-";
// protected static final String dataPrefix = "data-";
private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting
// manages the key/val arrays
@ -282,14 +282,14 @@ public class Attributes implements Iterable<Attribute>, Cloneable {
return Collections.unmodifiableList(list);
}
/**
* Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys
* starting with {@code data-}.
* @return map of custom data attributes.
*/
public Map<String, String> dataset() {
return new Dataset(this);
}
// /**
// * Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys
// * starting with {@code data-}.
// * @return map of custom data attributes.
// */
// public Map<String, String> dataset() {
// return new Dataset(this);
// }
// /**
// Get the HTML representation of these attributes.
@ -380,65 +380,65 @@ public class Attributes implements Iterable<Attribute>, Cloneable {
}
}
private static class Dataset extends AbstractMap<String, String> {
private final Attributes attributes;
// private static class Dataset extends AbstractMap<String, String> {
// private final Attributes attributes;
//
// private Dataset(Attributes attributes) {
// this.attributes = attributes;
// }
//
// @Override
// public Set<Entry<String, String>> entrySet() {
// return new EntrySet();
// }
//
// @Override
// public String put(String key, String value) {
// String dataKey = dataKey(key);
// String oldValue = attributes.hasKey(dataKey) ? attributes.get(dataKey) : null;
// attributes.put(dataKey, value);
// return oldValue;
// }
//
// private class EntrySet extends AbstractSet<Map.Entry<String, String>> {
//
// @Override
// public Iterator<Map.Entry<String, String>> iterator() {
// return new DatasetIterator();
// }
//
// @Override
// public int size() {
// int count = 0;
// Iterator iter = new DatasetIterator();
// while (iter.hasNext())
// count++;
// return count;
// }
// }
//
// private class DatasetIterator implements Iterator<Map.Entry<String, String>> {
// private Iterator<Attribute> attrIter = attributes.iterator();
// private Attribute attr;
// public boolean hasNext() {
// while (attrIter.hasNext()) {
// attr = attrIter.next();
// if (attr.isDataAttribute()) return true;
// }
// return false;
// }
//
// public Entry<String, String> next() {
// return new Attribute(attr.getKey().substring(dataPrefix.length()), attr.getValue());
// }
//
// public void remove() {
// attributes.remove(attr.getKey());
// }
// }
// }
private Dataset(Attributes attributes) {
this.attributes = attributes;
}
@Override
public Set<Entry<String, String>> entrySet() {
return new EntrySet();
}
@Override
public String put(String key, String value) {
String dataKey = dataKey(key);
String oldValue = attributes.hasKey(dataKey) ? attributes.get(dataKey) : null;
attributes.put(dataKey, value);
return oldValue;
}
private class EntrySet extends AbstractSet<Map.Entry<String, String>> {
@Override
public Iterator<Map.Entry<String, String>> iterator() {
return new DatasetIterator();
}
@Override
public int size() {
int count = 0;
Iterator iter = new DatasetIterator();
while (iter.hasNext())
count++;
return count;
}
}
private class DatasetIterator implements Iterator<Map.Entry<String, String>> {
private Iterator<Attribute> attrIter = attributes.iterator();
private Attribute attr;
public boolean hasNext() {
while (attrIter.hasNext()) {
attr = attrIter.next();
if (attr.isDataAttribute()) return true;
}
return false;
}
public Entry<String, String> next() {
return new Attribute(attr.getKey().substring(dataPrefix.length()), attr.getValue());
}
public void remove() {
attributes.remove(attr.getKey());
}
}
}
private static String dataKey(String key) {
return dataPrefix + key;
}
// private static String dataKey(String key) {
// return dataPrefix + key;
// }
}