Как получить атрибут из XMLReader
у меня есть HTML, который я конвертирую в Spanned
используя Html.fromHtml(...)
и у меня есть пользовательский тег, который я использую в нем:
<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();
}
}
в этом случае я получаю исключение SAX, поскольку я считаю, что поле " id " на самом деле является атрибутом, а не свойством. Тем не менее, нет getAttribute()
метод XMLReader
. Поэтому мой вопрос в том, как получить значение поля "id", используя это XMLReader
? Спасибо.
5 ответов
вот мой код, чтобы получить частный атрибуты 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, я сделал это немного более надежное решение, которое будет обрабатывать любой тег.
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);
...
и тогда атрибуты будут доступны следующим образом:
атрибуты.get ("имя атрибута");
можно использовать XmlReader
предоставлен TagHandler
и получить доступ к значениям атрибутов тегов без отражения, но этот метод еще менее прост, чем отражение. Фокус в том, чтобы заменить ContentHandler
используется XmlReader
С настраиваемого объекта. Замена ContentHandler
можно сделать только в вызове 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
объекты не создаются:
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");
}
});
существует альтернатива другим решениям, которая не позволяет использовать пользовательские теги, но имеет тот же эффект:
<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
to Spanned
интерфейс для исследования.
отметим, что Annotation
представляют <annotation foo="bar">...</annotation>
является Android встроенный начиная с уровня API 1! Это снова один из тех скрытых камней. он имеет ограничение одного атрибута на <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
также позволяет выполнять простой поиск и замену замены:
index = TextUtils.indexOf(text, needle); // for example $$username$$ above
text.replace(index, index + needle.length(), replacement);
Если все, что вам нужно, это просто один атрибут предложение vorrtex на самом деле довольно прочные. Чтобы дать вам пример того, как просто было бы справиться, посмотрите здесь:
<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);
}
и затем вы можете передать значение postfix в свой параметр onclick view в качестве тега, чтобы сохранить его общим.