Add attributes to start tags
This commit is contained in:
parent
bf8ff03b1c
commit
be484765da
@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see Inline
|
* @see Inline
|
||||||
@ -32,6 +33,9 @@ public interface HtmlTag {
|
|||||||
*/
|
*/
|
||||||
boolean isEmpty();
|
boolean isEmpty();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Map<String, String> attributes();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents <em>really</em> inline HTML tags (unline commonmark definitions)
|
* Represents <em>really</em> inline HTML tags (unline commonmark definitions)
|
||||||
*/
|
*/
|
||||||
|
@ -5,6 +5,7 @@ import android.support.annotation.Nullable;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
abstract class HtmlTagImpl implements HtmlTag {
|
abstract class HtmlTagImpl implements HtmlTag {
|
||||||
|
|
||||||
@ -12,11 +13,13 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final int start;
|
final int start;
|
||||||
|
final Map<String, String> attributes;
|
||||||
int end = NO_VALUE;
|
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.name = name;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -40,16 +43,23 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
return start == end;
|
return start == end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Map<String, String> attributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
boolean isClosed() {
|
boolean isClosed() {
|
||||||
return end > NO_VALUE;
|
return end > NO_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void closeAt(int end);
|
abstract void closeAt(int end);
|
||||||
|
|
||||||
|
|
||||||
static class InlineImpl extends HtmlTagImpl implements Inline {
|
static class InlineImpl extends HtmlTagImpl implements Inline {
|
||||||
|
|
||||||
InlineImpl(@NonNull String name, int start) {
|
InlineImpl(@NonNull String name, int start, @NonNull Map<String, String> attributes) {
|
||||||
super(name, start);
|
super(name, start, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -65,6 +75,7 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
"name='" + name + '\'' +
|
"name='" + name + '\'' +
|
||||||
", start=" + start +
|
", start=" + start +
|
||||||
", end=" + end +
|
", end=" + end +
|
||||||
|
", attributes=" + attributes +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,20 +85,28 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
@NonNull
|
@NonNull
|
||||||
static BlockImpl root() {
|
static BlockImpl root() {
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
return new BlockImpl("", 0, null);
|
return new BlockImpl("", 0, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static BlockImpl create(@NonNull String name, int start, @NonNull BlockImpl parent) {
|
static BlockImpl create(
|
||||||
return new BlockImpl(name, start, parent);
|
@NonNull String name,
|
||||||
|
int start,
|
||||||
|
@NonNull Map<String, String> attributes,
|
||||||
|
@NonNull BlockImpl parent) {
|
||||||
|
return new BlockImpl(name, start, attributes, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
final BlockImpl parent;
|
final BlockImpl parent;
|
||||||
List<BlockImpl> children;
|
List<BlockImpl> children;
|
||||||
|
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
BlockImpl(@NonNull String name, int start, @NonNull BlockImpl parent) {
|
BlockImpl(
|
||||||
super(name, start);
|
@NonNull String name,
|
||||||
|
int start,
|
||||||
|
@NonNull Map<String, String> attributes,
|
||||||
|
@NonNull BlockImpl parent) {
|
||||||
|
super(name, start, attributes);
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,12 +146,24 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
return (List<Block>) (List<? extends Block>) children;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "BlockImpl{" +
|
return "BlockImpl{" +
|
||||||
"name='" + name + '\'' +
|
"name='" + name + '\'' +
|
||||||
", start=" + start +
|
", start=" + start +
|
||||||
", end=" + end +
|
", end=" + end +
|
||||||
|
", attributes=" + attributes +
|
||||||
", parent=" + (parent != null ? parent.name : null) +
|
", parent=" + (parent != null ? parent.name : null) +
|
||||||
", children=" + children +
|
", children=" + children +
|
||||||
'}';
|
'}';
|
||||||
|
@ -7,14 +7,19 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import ru.noties.markwon.html.HtmlTag.Block;
|
import ru.noties.markwon.html.HtmlTag.Block;
|
||||||
import ru.noties.markwon.html.HtmlTag.Inline;
|
import ru.noties.markwon.html.HtmlTag.Inline;
|
||||||
import ru.noties.markwon.html.HtmlTagImpl.BlockImpl;
|
import ru.noties.markwon.html.HtmlTagImpl.BlockImpl;
|
||||||
import ru.noties.markwon.html.HtmlTagImpl.InlineImpl;
|
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.CharacterReader;
|
||||||
import ru.noties.markwon.html.jsoup.parser.ParseErrorList;
|
import ru.noties.markwon.html.jsoup.parser.ParseErrorList;
|
||||||
import ru.noties.markwon.html.jsoup.parser.Token;
|
import ru.noties.markwon.html.jsoup.parser.Token;
|
||||||
@ -209,7 +214,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
|
|||||||
|
|
||||||
final String name = startTag.normalName;
|
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)
|
if (isVoidTag(name)
|
||||||
|| startTag.selfClosing) {
|
|| startTag.selfClosing) {
|
||||||
@ -271,7 +276,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
|
|||||||
|
|
||||||
final int start = output.length();
|
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;
|
final boolean isVoid = isVoidTag(name) || startTag.selfClosing;
|
||||||
if (isVoid) {
|
if (isVoid) {
|
||||||
@ -396,4 +401,25 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
|
|||||||
append(output, "\n");
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// return new Attribute(unencodedKey, value, null); // parent will get set when Put
|
||||||
// }
|
// }
|
||||||
|
|
||||||
protected boolean isDataAttribute() {
|
// protected boolean isDataAttribute() {
|
||||||
return isDataAttribute(key);
|
// return isDataAttribute(key);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
protected static boolean isDataAttribute(String key) {
|
// protected static boolean isDataAttribute(String key) {
|
||||||
return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length();
|
// 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
|
// * Collapsible if it's a boolean attribute and value is empty or same as name
|
||||||
|
@ -28,7 +28,7 @@ import static ru.noties.markwon.html.jsoup.helper.Normalizer.lowerCase;
|
|||||||
* @author Jonathan Hedley, jonathan@hedley.net
|
* @author Jonathan Hedley, jonathan@hedley.net
|
||||||
*/
|
*/
|
||||||
public class Attributes implements Iterable<Attribute>, Cloneable {
|
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
|
private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting
|
||||||
|
|
||||||
// manages the key/val arrays
|
// manages the key/val arrays
|
||||||
@ -282,14 +282,14 @@ public class Attributes implements Iterable<Attribute>, Cloneable {
|
|||||||
return Collections.unmodifiableList(list);
|
return Collections.unmodifiableList(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys
|
// * Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys
|
||||||
* starting with {@code data-}.
|
// * starting with {@code data-}.
|
||||||
* @return map of custom data attributes.
|
// * @return map of custom data attributes.
|
||||||
*/
|
// */
|
||||||
public Map<String, String> dataset() {
|
// public Map<String, String> dataset() {
|
||||||
return new Dataset(this);
|
// return new Dataset(this);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// Get the HTML representation of these attributes.
|
// 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 static class Dataset extends AbstractMap<String, String> {
|
||||||
private final Attributes attributes;
|
// 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) {
|
// private static String dataKey(String key) {
|
||||||
this.attributes = attributes;
|
// return dataPrefix + key;
|
||||||
}
|
// }
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user