diff --git a/html-parser-api/src/main/java/ru/noties/markwon/html/HtmlTag.java b/html-parser-api/src/main/java/ru/noties/markwon/html/HtmlTag.java index 153b4a77..06712012 100644 --- a/html-parser-api/src/main/java/ru/noties/markwon/html/HtmlTag.java +++ b/html-parser-api/src/main/java/ru/noties/markwon/html/HtmlTag.java @@ -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 attributes(); + /** * Represents really inline HTML tags (unline commonmark definitions) */ diff --git a/html-parser-impl/src/main/java/ru/noties/markwon/html/HtmlTagImpl.java b/html-parser-impl/src/main/java/ru/noties/markwon/html/HtmlTagImpl.java index 4f6ca844..83a77173 100644 --- a/html-parser-impl/src/main/java/ru/noties/markwon/html/HtmlTagImpl.java +++ b/html-parser-impl/src/main/java/ru/noties/markwon/html/HtmlTagImpl.java @@ -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 attributes; int end = NO_VALUE; - protected HtmlTagImpl(@NonNull String name, int start) { + protected HtmlTagImpl(@NonNull String name, int start, @NonNull Map 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 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 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 attributes, + @NonNull BlockImpl parent) { + return new BlockImpl(name, start, attributes, parent); } final BlockImpl parent; List children; @SuppressWarnings("NullableProblems") - BlockImpl(@NonNull String name, int start, @NonNull BlockImpl parent) { - super(name, start); + BlockImpl( + @NonNull String name, + int start, + @NonNull Map attributes, + @NonNull BlockImpl parent) { + super(name, start, attributes); this.parent = parent; } @@ -127,12 +146,24 @@ abstract class HtmlTagImpl implements HtmlTag { return (List) (List) children; } + @NonNull + @Override + public Map 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 + '}'; diff --git a/html-parser-impl/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserImpl.java b/html-parser-impl/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserImpl.java index b6b731a8..72aefad6 100644 --- a/html-parser-impl/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserImpl.java +++ b/html-parser-impl/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserImpl.java @@ -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 extractAttributes(@NonNull Token.StartTag startTag) { + + Map 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; + } } diff --git a/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attribute.java b/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attribute.java index fea596e2..934dc364 100644 --- a/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attribute.java +++ b/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attribute.java @@ -137,13 +137,13 @@ public class Attribute implements Map.Entry, 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 diff --git a/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attributes.java b/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attributes.java index f00ecfe1..d91033e5 100644 --- a/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attributes.java +++ b/html-parser-impl/src/main/java/ru/noties/markwon/html/jsoup/nodes/Attributes.java @@ -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, 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, 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 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 dataset() { +// return new Dataset(this); +// } // /** // Get the HTML representation of these attributes. @@ -380,65 +380,65 @@ public class Attributes implements Iterable, Cloneable { } } - private static class Dataset extends AbstractMap { - private final Attributes attributes; +// private static class Dataset extends AbstractMap { +// private final Attributes attributes; +// +// private Dataset(Attributes attributes) { +// this.attributes = attributes; +// } +// +// @Override +// public Set> 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> { +// +// @Override +// public Iterator> 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> { +// private Iterator attrIter = attributes.iterator(); +// private Attribute attr; +// public boolean hasNext() { +// while (attrIter.hasNext()) { +// attr = attrIter.next(); +// if (attr.isDataAttribute()) return true; +// } +// return false; +// } +// +// public Entry 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> 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> { - - @Override - public Iterator> 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> { - private Iterator attrIter = attributes.iterator(); - private Attribute attr; - public boolean hasNext() { - while (attrIter.hasNext()) { - attr = attrIter.next(); - if (attr.isDataAttribute()) return true; - } - return false; - } - - public Entry 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; +// } } \ No newline at end of file