/talkback_preics/src/com/google/android/marvin/talkback/formatter/WebContentHandler.java
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}