/talkback_preics/src/com/google/android/marvin/talkback/formatter/WebContentFormatter.java
Java | 190 lines | 107 code | 25 blank | 58 comment | 7 complexity | 12fb8a21c1695a16585bd0873298aeb1 MD5 | raw file
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.android.marvin.talkback.formatter; 18 19import com.google.android.marvin.talkback.Formatter; 20import com.google.android.marvin.talkback.R; 21import com.google.android.marvin.talkback.Utils; 22import com.google.android.marvin.talkback.Utterance; 23 24import org.xml.sax.SAXException; 25 26import android.content.Context; 27import android.text.TextUtils; 28import android.util.Xml; 29import android.view.accessibility.AccessibilityEvent; 30 31import java.util.regex.Pattern; 32 33/** 34 * Formatter for web content. 35 * 36 * @author svetoslavganov@google.com (Svetoslav Ganov) 37 * @author credo@google.com (Tim Credo) 38 */ 39@SuppressWarnings("unused") 40public final class WebContentFormatter implements Formatter { 41 42 private static final int ACTION_SET_CURRENT_AXIS = 0; 43 private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1; 44 private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2; 45 private static final int ACTION_PERFORM_AXIS_TRANSITION = 3; 46 private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4; 47 48 private static String[] sAxisNames; 49 50 /** 51 * A template to apply to markup before sending it to an XML parser. 52 */ 53 private static final String XML_TEMPLATE = 54 "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><div>%s</div>"; 55 56 /** 57 * Regular expression that matches all HTML tags. 58 */ 59 private final Pattern mStripMarkupPattern = Pattern.compile("<(.)+?>"); 60 61 /** 62 * Regular expression that matches all entity codes. 63 */ 64 private final Pattern mStripEntitiesPattern = Pattern.compile("&(.)+?;"); 65 66 /** 67 * Regular expression that matches all div or span tags. 68 */ 69 private final Pattern mStripDivSpanPattern = Pattern.compile( 70 "</?(div|span).*?>", Pattern.CASE_INSENSITIVE); 71 72 /** 73 * Regular expression that matches some common singleton tags. 74 */ 75 private final Pattern mCloseTagPattern = Pattern.compile( 76 "(<(img|input|br).+?)>", Pattern.CASE_INSENSITIVE); 77 78 /** 79 * A handler for processing HTML and generating output for speaking. 80 */ 81 private WebContentHandler mHtmlHandler = null; 82 83 84 private final Action mTempAction = new Action(); 85 86 @Override 87 public void format(AccessibilityEvent event, Context context, Utterance utterance, 88 Object args) { 89 // for now ... lookup and announce axis transitions 90 CharSequence contentDescription = event.getContentDescription(); 91 if (TextUtils.isEmpty(contentDescription)) { 92 return; 93 } 94 Action action = mTempAction; 95 action.init(contentDescription.toString()); 96 int actionCode = mTempAction.mActionCode; 97 if (actionCode == ACTION_PERFORM_AXIS_TRANSITION) { 98 String axisAnnouncement = getAxisAnnouncement(context, action.mSecondArgument); 99 utterance.getText().append(axisAnnouncement); 100 return; 101 } 102 // for now ... disregard content description 103 String markup = Utils.getEventText(context, event).toString(); 104 String noTags = mStripMarkupPattern.matcher(markup).replaceAll(""); 105 String cleaned = cleanMarkup(markup); 106 if (mHtmlHandler == null) { 107 mHtmlHandler = new TalkBackWebContentHandler(context.getResources()); 108 } 109 try { 110 Xml.parse(cleaned, mHtmlHandler); 111 String speech = mHtmlHandler.getOutput(); 112 utterance.getText().append(speech); 113 } catch (SAXException e) { 114 e.printStackTrace(); 115 utterance.getText().append(noTags); 116 } 117 } 118 119 /** 120 * Process HTML to remove markup that can't be handled by the SAX parser. 121 * 122 * @param markup Input HTML generated by system. 123 * @return A string of cleaned HTML. 124 */ 125 public String cleanMarkup(String markup) { 126 String noDivOrSpan = mStripDivSpanPattern.matcher(markup).replaceAll(""); 127 String noEntities = mStripEntitiesPattern.matcher(noDivOrSpan).replaceAll(" "); 128 String tagsClosed = mCloseTagPattern.matcher(noEntities).replaceAll("$1/>"); 129 return String.format(XML_TEMPLATE, tagsClosed); 130 } 131 132 /** 133 * Gets an announcement for a navigation axis given its code. 134 * 135 * @param context Context for loading resources. 136 * @param axisCode The code the the axis. 137 * @return The axis announcement. 138 */ 139 private String getAxisAnnouncement(Context context, int axisCode) { 140 if (sAxisNames == null) { 141 sAxisNames = new String[] { 142 context.getString(R.string.axis_character), 143 context.getString(R.string.axis_word), 144 context.getString(R.string.axis_sentence), 145 context.getString(R.string.axis_heading), 146 context.getString(R.string.axis_sibling), 147 context.getString(R.string.axis_parent_first_child), 148 context.getString(R.string.axis_document), 149 context.getString(R.string.axis_default_web_view_behavior) 150 }; 151 } 152 return sAxisNames[axisCode]; 153 } 154 155 /** 156 * Represents an action. 157 */ 158 private class Action { 159 private static final int ACTION_OFFSET = 24; 160 private static final int ACTION_MASK = 0xFF000000; 161 162 private static final int FIRST_ARGUMENT_OFFSET = 16; 163 private static final int FIRST_ARGUMENT_MASK = 0x00FF0000; 164 165 private static final int SECOND_ARGUMENT_OFFSET = 8; 166 private static final int SECOND_ARGUMENT_MASK = 0x0000FF00; 167 168 private static final int THIRD_ARGUMENT_OFFSET = 0; 169 private static final int THIRD_ARGUMENT_MASK = 0x000000FF; 170 171 private int mActionCode; 172 private int mFirstArgument; 173 private int mSecondArgument; 174 private int mThirdArgument; 175 176 public void init(String encodedActionString) { 177 int encodedAction = 0; 178 try { 179 // hack 180 encodedAction = Integer.decode("0x" + encodedActionString); 181 } catch (NumberFormatException nfe) { 182 return; 183 } 184 mActionCode = (encodedAction & ACTION_MASK) >> ACTION_OFFSET; 185 mFirstArgument = (encodedAction & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET; 186 mSecondArgument = (encodedAction & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET; 187 mThirdArgument = (encodedAction & THIRD_ARGUMENT_OFFSET) >> THIRD_ARGUMENT_MASK; 188 } 189 } 190}