如何从XMLReader获取属性

我有一些使用Html.fromHtml(...)转换为Html.fromHtml(...) HTML,并且我使用了一个自定义标签:

 <customtag id="1234"> 

所以我已经实现了一个TagHandler来处理这个自定义标签,就像这样:

 public void handleTag( boolean opening, String tag, Editable output, XMLReader xmlReader ) { if ( tag.equalsIgnoreCase( "customtag" ) ) { String id = xmlReader.getProperty( "id" ).toString(); } } 

在这种情况下,我得到一个SAXexception,因为我相信“id”字段实际上是一个属性,而不是属性。 但是, XMLReader没有getAttribute()方法。 所以我的问题是,如何获得使用此XMLReader的“ID”字段的值? 谢谢。

Solutions Collecting From Web of "如何从XMLReader获取属性"

这里是我的代码通过reflection来获取xmlReader的私有属性:

 Field elementField = xmlReader.getClass().getDeclaredField("theNewElement"); elementField.setAccessible(true); Object element = elementField.get(xmlReader); Field attsField = element.getClass().getDeclaredField("theAtts"); attsField.setAccessible(true); Object atts = attsField.get(element); Field dataField = atts.getClass().getDeclaredField("data"); dataField.setAccessible(true); String[] data = (String[])dataField.get(atts); Field lengthField = atts.getClass().getDeclaredField("length"); lengthField.setAccessible(true); int len = (Integer)lengthField.get(atts); String myAttributeA = null; String myAttributeB = null; for(int i = 0; i < len; i++) { if("attrA".equals(data[i * 5 + 1])) { myAttributeA = data[i * 5 + 4]; } else if("attrB".equals(data[i * 5 + 1])) { myAttributeB = data[i * 5 + 4]; } } 

请注意,您可以将这些值放入地图中,但是对于我的使用情况来说,开销太大了。

基于rekire的答案,我做了这个稍微更强大的解决scheme,将处理任何标签。

 private TagHandler tagHandler = new TagHandler() { final HashMap<String, String> attributes = new HashMap<String, String>(); private void processAttributes(final XMLReader xmlReader) { try { Field elementField = xmlReader.getClass().getDeclaredField("theNewElement"); elementField.setAccessible(true); Object element = elementField.get(xmlReader); Field attsField = element.getClass().getDeclaredField("theAtts"); attsField.setAccessible(true); Object atts = attsField.get(element); Field dataField = atts.getClass().getDeclaredField("data"); dataField.setAccessible(true); String[] data = (String[])dataField.get(atts); Field lengthField = atts.getClass().getDeclaredField("length"); lengthField.setAccessible(true); int len = (Integer)lengthField.get(atts); /** * MSH: Look for supported attributes and add to hash map. * This is as tight as things can get :) * The data index is "just" where the keys and values are stored. */ for(int i = 0; i < len; i++) attributes.put(data[i * 5 + 1], data[i * 5 + 4]); } catch (Exception e) { Log.d(TAG, "Exception: " + e); } } ... 

并在里面handleTag做:

  @Override public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { processAttributes(xmlReader); ... 

然后这些属性将可以被访问:

attributes.get(“my attribute name”);

有可能使用TagHandler提供的XmlReader ,并且无需reflection即可访问标签属性值,但是这种方法比reflection更简单。 诀窍是用自定义对象replaceXmlReader使用的ContentHandler 。 replaceContentHandler只能在handleTag()的调用中完成。 这就出现了获取第一个标签属性值的问题,可以通过在html开头添加一个自定义标签来解决。

 import android.text.Editable; import android.text.Html; import android.text.Spanned; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import java.util.ArrayDeque; public class HtmlParser implements Html.TagHandler, ContentHandler { public interface TagHandler { boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes); } public static Spanned buildSpannedText(String html, TagHandler handler) { // add a tag at the start that is not handled by default, // allowing custom tag handler to replace xmlReader contentHandler return Html.fromHtml("<inject/>" + html, null, new HtmlParser(handler)); } public static String getValue(Attributes attributes, String name) { for (int i = 0, n = attributes.getLength(); i < n; i++) { if (name.equals(attributes.getLocalName(i))) return attributes.getValue(i); } return null; } private final TagHandler handler; private ContentHandler wrapped; private Editable text; private ArrayDeque<Boolean> tagStatus = new ArrayDeque<>(); private HtmlParser(TagHandler handler) { this.handler = handler; } @Override public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { if (wrapped == null) { // record result object text = output; // record current content handler wrapped = xmlReader.getContentHandler(); // replace content handler with our own that forwards to calls to original when needed xmlReader.setContentHandler(this); // handle endElement() callback for <inject/> tag tagStatus.addLast(Boolean.FALSE); } } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { boolean isHandled = handler.handleTag(true, localName, text, attributes); tagStatus.addLast(isHandled); if (!isHandled) wrapped.startElement(uri, localName, qName, attributes); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (!tagStatus.removeLast()) wrapped.endElement(uri, localName, qName); handler.handleTag(false, localName, text, null); } @Override public void setDocumentLocator(Locator locator) { wrapped.setDocumentLocator(locator); } @Override public void startDocument() throws SAXException { wrapped.startDocument(); } @Override public void endDocument() throws SAXException { wrapped.endDocument(); } @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { wrapped.startPrefixMapping(prefix, uri); } @Override public void endPrefixMapping(String prefix) throws SAXException { wrapped.endPrefixMapping(prefix); } @Override public void characters(char[] ch, int start, int length) throws SAXException { wrapped.characters(ch, start, length); } @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { wrapped.ignorableWhitespace(ch, start, length); } @Override public void processingInstruction(String target, String data) throws SAXException { wrapped.processingInstruction(target, data); } @Override public void skippedEntity(String name) throws SAXException { wrapped.skippedEntity(name); } } 

有了这个类阅读属性很容易:

  HtmlParser.buildSpannedText("<x id=1 value=a>test<x id=2 value=b>", new HtmlParser.TagHandler() { @Override public boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes) { if (opening && tag.equals("x")) { String id = HtmlParser.getValue(attributes, "id"); String value = HtmlParser.getValue(attributes, "value"); } return false; } }); 

这种方法的优点是,它允许禁用某些标签的处理,而对其他标签使用默认处理,例如,您可以确保ImageSpan对象不被创build:

  Spanned result = HtmlParser.buildSpannedText("<b><img src=nothing>test</b><img src=zilch>", new HtmlParser.TagHandler() { @Override public boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes) { // return true here to indicate that this tag was handled and // should not be processed further return tag.equals("img"); } }); 

有其他解决scheme的替代scheme,不允许您使用自定义标签,但具有相同的效果:

 <string name="foobar">blah <annotation customTag="1234">inside blah</annotation> more blah</string> 

然后阅读它是这样的:

 CharSequence annotatedText = context.getText(R.string.foobar); // wrap, because getText returns a SpannedString, which is not mutable CharSequence processedText = replaceCustomTags(new SpannableStringBuilder(annotatedText)); public static <T extends Spannable> T replaceCustomTags(T text) { Annotation[] annotations = text.getSpans(0, text.length(), Annotation.class); for (Annotation a : annotations) { String attrName = a.getKey(); if ("customTag".equals(attrName)) { String attrValue = a.getValue(); int contentStart = text.getSpanStart(a); int contentEnd = text.getSpanEnd(a); int contentFlags = text.getSpanFlags(a); Object newFormat1 = new StyleSpan(Typeface.BOLD); Object newFormat2 = new ForegroundColorSpan(Color.RED); text.setSpan(newFormat1, contentStart, contentEnd, contentFlags); text.setSpan(newFormat2, contentStart, contentEnd, contentFlags); text.removeSpan(a); } } return text; } 

根据你想要做什么你的自定义标签,上面可能会帮助你。 如果你只是想阅读它们,你不需要一个SpannableStringBuilder ,只需将getText SpannableStringBuilderSpannableStringBuilder接口来进行调查。

注意代表<annotation foo="bar">...</annotation>是API级别1以来的Android内置! 这是那些隐藏的gem之一。 它具有每个<annotation>标签一个属性的限制,但是没有任何东西阻止你嵌套多个注释来实现多个属性:

 <string name="gold_admin_user"><annotation user="admin"><annotation rank="gold">$$username$$</annotation></annotation></string> 

如果使用Editable界面而不是Spannable ,则还可以修改每个注释周围的内容。 例如更改上面的代码:

 String attrValue = a.getValue(); text.insert(text.getSpanStart(a), attrValue); text.insert(text.getSpanStart(a) + attrValue.length(), " "); int contentStart = text.getSpanStart(a); 

将导致您在XML中有这样的结果:

 blah <b><font color="#ff0000">1234 inside blah</font></b> more blah 

要注意的一个警告是当你修改影响文本的长度时,跨度移动。 确保您在正确的时间读取跨度开始/结束索引,如果您将它们内联到方法调用中,则最好。

Editable也可以让你做简单的search和replacereplace:

 index = TextUtils.indexOf(text, needle); // for example $$username$$ above text.replace(index, index + needle.length(), replacement); 

如果你所需要的仅仅是一个属性,vorrtex的build议其实是非常可靠的。 给你一个简单的例子来处理看看这里:

 <xml>Click on <user1>Johnni<user1> or <user2>Jenny<user2> to see...</<xml> 

而在你的自定义TagHandler中,你不使用equals而是indexOf

 final static String USER = "user"; if(tag.indexOf(USER) == 0) { // Extract tag postfix. String postfix = tag.substring(USER.length()); Log.d(TAG, "postfix: " + postfix); } 

然后,您可以将onClick视图参数中的postfix值作为标记传递,以保持其通用性。