Java 如何从XMLReader获取属性

Java 如何从XMLReader获取属性,java,android,xmlreader,tag-soup,Java,Android,Xmlreader,Tag Soup,我用HTML.fromHtml(…)将一些HTML转换为span,并且我在其中使用了一个自定义标记: <customtag id="1234"> 在本例中,我得到一个SAX异常,因为我认为“id”字段实际上是一个属性,而不是一个属性。但是,对于XMLReader,没有getAttribute()方法。所以我的问题是,如何使用这个XMLReader获取“id”字段的值?谢谢。以下是我通过反射获取xmlReader的私有属性的代码: Field elementField = xmlRe

我用
HTML.fromHtml(…)
将一些HTML转换为
span
,并且我在其中使用了一个自定义标记:

<customtag id="1234">

在本例中,我得到一个SAX异常,因为我认为“id”字段实际上是一个属性,而不是一个属性。但是,对于
XMLReader
,没有
getAttribute()
方法。所以我的问题是,如何使用这个
XMLReader
获取“id”字段的值?谢谢。

以下是我通过反射获取
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];
    }
}
fieldelementfield=xmlReader.getClass().getDeclaredField(“theNewElement”);
elementField.setAccessible(true);
objectelement=elementField.get(xmlReader);
字段attsField=element.getClass().getDeclaredField(“theAtts”);
attsField.setAccessible(真);
对象atts=attsField.get(元素);
Field dataField=atts.getClass().getDeclaredField(“数据”);
dataField.setAccessible(true);
字符串[]数据=(字符串[])数据字段.get(atts);
字段长度字段=atts.getClass().getDeclaredField(“长度”);
lengthField.setAccessible(真);
int len=(整数)lengthField.get(atts);
字符串myAttributeA=null;
字符串myAttributeB=null;
对于(int i=0;i

请注意,您可以将这些值放入地图中,但对于我的使用,这会带来太多开销。

如果您只需要一个属性,那么Vortex的建议实际上非常可靠。要给您一个简单的处理示例,请看这里:

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

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

根据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);
        }
    }
...
然后属性将可以访问,如下所示:


获取(“我的属性名称”)

可以使用
TagHandler
提供的
XmlReader
,在不进行反射的情况下访问标记属性值,但该方法甚至不如反射简单。诀窍是用自定义对象替换
XmlReader
使用的
ContentHandler
。只能通过调用
handleTag()
来替换
ContentHandler
。这会出现获取第一个标记的属性值的问题,可以通过在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);
    }
}

其他解决方案还有一种替代方案,不允许您使用自定义标记,但具有相同的效果:

<string name="foobar">blah <annotation customTag="1234">inside blah</annotation> more blah</string>
如果使用
可编辑
界面而不是
span可编辑
,还可以修改每个批注周围的内容。例如,更改上述代码:

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

哪里是
TagHandler
?执行SAX2的通常方法是使用
ContentHandler
s,当通过
HTML.fromHtml(String,ImageGetter,TagHandler)
将HTML文本转换为可扩展文本时,不使用
TagHandler
。它用于处理未知标记(TagSoup无法识别标记)。我明白了。我只是用TagSoup标记了这个问题,这样熟悉这个解析器的人就可以找到这个问题。我确实知道,在标准Java库中的常规SAX2解析器中,您只需设置ContentHandlers,而不是TagHandlers,startElement回调已经存在属性。我也遇到了同样的问题,当我查看Android源代码时,我看到这些属性是故意不传递的。因此,我用具有特定名称的其他标记替换带有属性的标记。就像你的情况一样。@rekire不,我没有。我最终做了vorrtex建议的事情。嗯,这会不断抛出java.lang.NoSuchFieldException-我做错了什么?@slott有三种可能性:字段不再存在(可能是不兼容的版本);通常,您正在访问一个私有字段,但忘记调用
setAccessible(true)
或者它是基类的一部分,在最后一种情况下,你需要检查它的超类。我让它工作了-诀窍不是在handleTag方法中作为第一件事调用:)Thanx寻求帮助-当有人把你推过人生的坎坷时总是有帮助的…我只是尝试了一下,处理程序部分工作得非常好,然而,内置的解析器不知怎么搞砸了,
text
被解析为
attributes={attr=“a”,“_=“”,b=“b”}
。使用
&也同样糟糕。@TWiStErRob for me
String testStr=“text”被正确解析。我通过解析
stringteststr=“text”得到了您描述的问题,这实际上不是一个有效的html。嗯,您使用的是硬编码的Java,我从参考资料中读取了值:
…]>。当框架读取XML时,可能在传输过程中丢失了一些东西。我通过在属性中使用Java标识符(例如enum常量名)而不是纯文本来解决这个问题,因此它们只是一个“单词”长。
    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;
        }
    });
    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;
}
<string name="gold_admin_user"><annotation user="admin"><annotation rank="gold">$$username$$</annotation></annotation></string>
String attrValue = a.getValue();
text.insert(text.getSpanStart(a), attrValue);
text.insert(text.getSpanStart(a) + attrValue.length(), " ");
int contentStart = text.getSpanStart(a);
blah <b><font color="#ff0000">1234 inside blah</font></b> more blah
index = TextUtils.indexOf(text, needle); // for example $$username$$ above
text.replace(index, index + needle.length(), replacement);