Cómo obtener un atributo de un XMLReader

Tengo un poco HTML que estoy convirtiendo a un Spanned usando Html.fromHtml(...) , y tengo una etiqueta de encargo que estoy utilizando en ella:

 <customtag id="1234"> 

Así que he implementado un TagHandler para manejar esta etiqueta personalizada, así:

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

En este caso recibo una excepción SAX, ya que creo que el campo "id" es realmente un atributo, no una propiedad. Sin embargo, no hay un método getAttribute() para XMLReader . Así que mi pregunta es, ¿cómo puedo obtener el valor del campo "id" utilizando este XMLReader ? Gracias.

Aquí está mi código para obtener los atributos privados del xmlReader por reflexión:

 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]; } } 

Tenga en cuenta que podría poner los valores en un mapa, pero para mi uso que es demasiado sobrecarga.

Basado en la respuesta de rekire hice esta solución ligeramente más robusta que se encargará de cualquier etiqueta.

 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); } } ... 

Y dentro handleTag hacer:

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

Y entonces los atributos serán accesibles como tal:

Attributes.get ("mi nombre de atributo");

Es posible utilizar XmlReader proporcionado por TagHandler y obtener acceso a valores de atributo de etiqueta sin reflexión, pero ese método es aún menos directo que la reflexión. El truco es reemplazar ContentHandler utilizado por XmlReader con objeto personalizado. La sustitución de ContentHandler sólo se puede realizar en la llamada a handleTag() . Que presenta un problema para obtener valores de atributo para la primera etiqueta, que se puede resolver mediante la adición de una etiqueta personalizada en el inicio de 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); } } 

Con esta clase los atributos de lectura son fáciles:

  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; } }); 

Este enfoque tiene la ventaja de que permite inhabilitar el procesamiento de algunas etiquetas mientras se utiliza el procesamiento predeterminado para otros, por ejemplo, puede asegurarse de que los objetos ImageSpan no se creen:

  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"); } }); 

Hay una alternativa a las otras soluciones, que no le permite utilizar etiquetas personalizadas, pero tiene el mismo efecto:

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

A continuación, lea de esta manera:

 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; } 

Dependiendo de lo que quisieras hacer con tus etiquetas personalizadas, lo anterior podría ayudarte. Si solo desea leerlos, no necesita un SpannableStringBuilder , simplemente Spanned interfaz getText a Spanned para investigar.

Tenga en cuenta que Annotation representando <annotation foo="bar">...</annotation> es un Android incorporado desde el nivel 1 de la API. Es una de esas joyas ocultas de nuevo. Tiene la limitación de un atributo por etiqueta <annotation> , pero nada le impide anidar varias anotaciones para lograr múltiples atributos:

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

Si utiliza la interfaz Editable en lugar de Spannable también puede modificar el contenido de cada anotación. Por ejemplo, cambiar el código anterior:

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

Resultará como si tuviera esto en el XML:

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

Una advertencia a tener en cuenta es cuando usted hace modificaciones que afectan a la longitud del texto, los tramos se mueven alrededor. Asegúrese de leer los índices de inicio / finalización del tramo en las horas correctas, mejor si las enumera con las llamadas al método.

Editable también le permite hacer la búsqueda simple y reemplazar la sustitución:

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

Si todo lo que necesita es sólo un atributo de la sugerencia de vorrtex es realmente bastante sólido. Para darle un ejemplo de lo sencillo que sería manejar, eche un vistazo aquí:

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

Y en tu TagHandler personalizado no utilizas iguales pero 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); } 

A continuación, puede pasar el valor postfix en su parámetro de vista onClick como una etiqueta para mantenerlo genérico.

  • Tarea Simple de Reconocimiento de Imágenes en Android: Dominó Lectura
  • Declaración de la capacidad de una lista en Java
  • Acceso / sdcard en Android 4.2
  • Cómo enviar correo electrónico con un enlace para abrir la aplicación de Android
  • Ejemplo de OpenCV Template Matching en Android
  • "Por qué Apache Harmony" o "Cómo usar Java 8 en Android"
  • Cómo corregir la advertencia proguard 'no se puede encontrar el método de referencia' para los métodos existentes 'clonar' y 'finalizar' de la clase java.lang.Object
  • Algunas preguntas sobre Gradle para Android Development
  • Excepción de tiempo de ejecución al utilizar la configuración de Android (estoy siguiendo el curso de desarrollo de Android de udacity)
  • OAuth: App Id o redirect_uri no coincide con el código de autorización
  • ¿Cómo comprobar si dos caminos están en el mismo punto de montaje?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.