PageRenderTime 30ms CodeModel.GetById 17ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/talkback_preics/src/com/google/android/marvin/talkback/formatter/WebContentHandler.java

http://eyes-free.googlecode.com/
Java | 170 lines | 91 code | 18 blank | 61 comment | 31 complexity | 596b2e20b47a128100a7c521f336d74f MD5 | raw file
  1// Copyright 2010 Google Inc. All Rights Reserved.
  2
  3package com.google.android.marvin.talkback.formatter;
  4
  5import org.xml.sax.Attributes;
  6import org.xml.sax.helpers.DefaultHandler;
  7
  8import java.util.HashMap;
  9import java.util.Stack;
 10
 11/**
 12 * A handler for parsing simple HTML from Android WebView.
 13 * 
 14 * @author credo@google.com (Tim Credo)
 15 */
 16public class WebContentHandler extends DefaultHandler {
 17    
 18    /**
 19     * Maps input type attribute to element description.
 20     */
 21    private HashMap<String, String> mInputTypeToDesc;
 22
 23    /**
 24     * Maps ARIA role attribute to element description.
 25     */
 26    private HashMap<String, String> mAriaRoleToDesc;
 27
 28    /**
 29     * Map tags to element description.
 30     */
 31    private HashMap<String, String> mTagToDesc;
 32
 33    /**
 34     * A stack for storing post-order text generated by opening tags.
 35     */
 36    private Stack<String> mPostorderTextStack;
 37
 38    /**
 39     * Builder for a string to be spoken based on parsed HTML.
 40     */
 41    private StringBuilder mOutputBuilder;
 42
 43    /**
 44     * Initializes the handler with maps that provide descriptions for relevant
 45     * features in HTML.
 46     * 
 47     * @param htmlInputMap A mapping from input types to text descriptions.
 48     * @param htmlRoleMap A mapping from ARIA roles to text descriptions.
 49     * @param htmlTagMap A mapping from common tags to text descriptions.
 50     */
 51    public WebContentHandler(HashMap<String, String> htmlInputMap,
 52            HashMap<String, String> htmlRoleMap, HashMap<String, String> htmlTagMap) {
 53        super();
 54        mInputTypeToDesc = htmlInputMap;
 55        mAriaRoleToDesc = htmlRoleMap;
 56        mTagToDesc = htmlTagMap;
 57    }
 58
 59    @Override
 60    public void startDocument() {
 61        mOutputBuilder = new StringBuilder();
 62        mPostorderTextStack = new Stack<String>();
 63    }
 64
 65    /**
 66     * Depending on the type of element, generate text describing its conceptual
 67     * value and role and add it to the output. The role text is spoken after
 68     * any content, so it is added to the stack to wait for the closing tag.
 69     */
 70    @Override
 71    public void startElement(String uri, String localName, String name, Attributes attributes) {
 72        fixWhiteSpace();
 73        String ariaLabel = attributes.getValue("aria-label");
 74        String alt = attributes.getValue("alt");
 75        String title = attributes.getValue("title");
 76        if (ariaLabel != null) {
 77            mOutputBuilder.append(ariaLabel);
 78        } else if (alt != null) {
 79            mOutputBuilder.append(alt);
 80        } else if (title != null) {
 81            mOutputBuilder.append(title);
 82        }
 83
 84        /*
 85         * Add role text to the stack so it appears after the content. If there
 86         * is no text we still need to push a blank string, since this will pop
 87         * when this element ends.
 88         */
 89        String role = attributes.getValue("role");
 90        String roleName = mAriaRoleToDesc.get(role);
 91        String type = attributes.getValue("type");
 92        String tagInfo = mTagToDesc.get(name.toLowerCase());
 93        if (roleName != null) {
 94            mPostorderTextStack.push(roleName);
 95        } else if (name.equalsIgnoreCase("input") && type != null) {
 96            String typeInfo = mInputTypeToDesc.get(type.toLowerCase());
 97            if (typeInfo != null) {
 98                mPostorderTextStack.push(typeInfo);
 99            } else {
100                mPostorderTextStack.push("");
101            }
102        } else if (tagInfo != null) {
103            mPostorderTextStack.push(tagInfo);
104        } else {
105            mPostorderTextStack.push("");
106        }
107
108        /*
109         * The value should be spoken as long as the element is not a form
110         * element with a non-human-readable value.
111         */
112        String value = attributes.getValue("value");
113        if (value != null) {
114            String elementType = name;
115            if (name.equalsIgnoreCase("input") && type != null) {
116                elementType = type;
117            }
118            if (!elementType.equalsIgnoreCase("checkbox") && !elementType.equalsIgnoreCase("radio")) {
119                fixWhiteSpace();
120                mOutputBuilder.append(value);
121            }
122        }
123    }
124
125    /**
126     * Character data is passed directly to output.
127     */
128    @Override
129    public void characters(char[] ch, int start, int length) {
130        mOutputBuilder.append(ch, start, length);
131    }
132
133    /**
134     * After the end of an element, get the post-order text from the stack and
135     * add it to the output.
136     */
137    @Override
138    public void endElement(String uri, String localName, String name) {
139        String postorderText = mPostorderTextStack.pop();
140        if (postorderText.length() > 0) {
141            fixWhiteSpace();
142        }
143        mOutputBuilder.append(postorderText);
144    }
145
146    /**
147     * Ensure the output string has a character of whitespace before adding
148     * another word.
149     */
150    public void fixWhiteSpace() {
151        int index = mOutputBuilder.length() - 1;
152        if (index >= 0) {
153            char lastCharacter = mOutputBuilder.charAt(index);
154            if (!Character.isWhitespace(lastCharacter)) {
155                mOutputBuilder.append(" ");
156            }
157        }
158    }
159
160    /**
161     * Get the processed string in mBuilder. Call this after parsing is done to
162     * get the finished output.
163     * 
164     * @return A string with HTML tags converted to descriptions suitable for
165     *         speaking.
166     */
167    public String getOutput() {
168        return mOutputBuilder.toString();
169    }
170}