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

http://eyes-free.googlecode.com/ · 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. package com.google.android.marvin.talkback.formatter;
  17. import com.google.android.marvin.talkback.Formatter;
  18. import com.google.android.marvin.talkback.R;
  19. import com.google.android.marvin.talkback.Utils;
  20. import com.google.android.marvin.talkback.Utterance;
  21. import org.xml.sax.SAXException;
  22. import android.content.Context;
  23. import android.text.TextUtils;
  24. import android.util.Xml;
  25. import android.view.accessibility.AccessibilityEvent;
  26. import java.util.regex.Pattern;
  27. /**
  28. * Formatter for web content.
  29. *
  30. * @author svetoslavganov@google.com (Svetoslav Ganov)
  31. * @author credo@google.com (Tim Credo)
  32. */
  33. @SuppressWarnings("unused")
  34. public final class WebContentFormatter implements Formatter {
  35. private static final int ACTION_SET_CURRENT_AXIS = 0;
  36. private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
  37. private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
  38. private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
  39. private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
  40. private static String[] sAxisNames;
  41. /**
  42. * A template to apply to markup before sending it to an XML parser.
  43. */
  44. private static final String XML_TEMPLATE =
  45. "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><div>%s</div>";
  46. /**
  47. * Regular expression that matches all HTML tags.
  48. */
  49. private final Pattern mStripMarkupPattern = Pattern.compile("<(.)+?>");
  50. /**
  51. * Regular expression that matches all entity codes.
  52. */
  53. private final Pattern mStripEntitiesPattern = Pattern.compile("&(.)+?;");
  54. /**
  55. * Regular expression that matches all div or span tags.
  56. */
  57. private final Pattern mStripDivSpanPattern = Pattern.compile(
  58. "</?(div|span).*?>", Pattern.CASE_INSENSITIVE);
  59. /**
  60. * Regular expression that matches some common singleton tags.
  61. */
  62. private final Pattern mCloseTagPattern = Pattern.compile(
  63. "(<(img|input|br).+?)>", Pattern.CASE_INSENSITIVE);
  64. /**
  65. * A handler for processing HTML and generating output for speaking.
  66. */
  67. private WebContentHandler mHtmlHandler = null;
  68. private final Action mTempAction = new Action();
  69. @Override
  70. public void format(AccessibilityEvent event, Context context, Utterance utterance,
  71. Object args) {
  72. // for now ... lookup and announce axis transitions
  73. CharSequence contentDescription = event.getContentDescription();
  74. if (TextUtils.isEmpty(contentDescription)) {
  75. return;
  76. }
  77. Action action = mTempAction;
  78. action.init(contentDescription.toString());
  79. int actionCode = mTempAction.mActionCode;
  80. if (actionCode == ACTION_PERFORM_AXIS_TRANSITION) {
  81. String axisAnnouncement = getAxisAnnouncement(context, action.mSecondArgument);
  82. utterance.getText().append(axisAnnouncement);
  83. return;
  84. }
  85. // for now ... disregard content description
  86. String markup = Utils.getEventText(context, event).toString();
  87. String noTags = mStripMarkupPattern.matcher(markup).replaceAll("");
  88. String cleaned = cleanMarkup(markup);
  89. if (mHtmlHandler == null) {
  90. mHtmlHandler = new TalkBackWebContentHandler(context.getResources());
  91. }
  92. try {
  93. Xml.parse(cleaned, mHtmlHandler);
  94. String speech = mHtmlHandler.getOutput();
  95. utterance.getText().append(speech);
  96. } catch (SAXException e) {
  97. e.printStackTrace();
  98. utterance.getText().append(noTags);
  99. }
  100. }
  101. /**
  102. * Process HTML to remove markup that can't be handled by the SAX parser.
  103. *
  104. * @param markup Input HTML generated by system.
  105. * @return A string of cleaned HTML.
  106. */
  107. public String cleanMarkup(String markup) {
  108. String noDivOrSpan = mStripDivSpanPattern.matcher(markup).replaceAll("");
  109. String noEntities = mStripEntitiesPattern.matcher(noDivOrSpan).replaceAll(" ");
  110. String tagsClosed = mCloseTagPattern.matcher(noEntities).replaceAll("$1/>");
  111. return String.format(XML_TEMPLATE, tagsClosed);
  112. }
  113. /**
  114. * Gets an announcement for a navigation axis given its code.
  115. *
  116. * @param context Context for loading resources.
  117. * @param axisCode The code the the axis.
  118. * @return The axis announcement.
  119. */
  120. private String getAxisAnnouncement(Context context, int axisCode) {
  121. if (sAxisNames == null) {
  122. sAxisNames = new String[] {
  123. context.getString(R.string.axis_character),
  124. context.getString(R.string.axis_word),
  125. context.getString(R.string.axis_sentence),
  126. context.getString(R.string.axis_heading),
  127. context.getString(R.string.axis_sibling),
  128. context.getString(R.string.axis_parent_first_child),
  129. context.getString(R.string.axis_document),
  130. context.getString(R.string.axis_default_web_view_behavior)
  131. };
  132. }
  133. return sAxisNames[axisCode];
  134. }
  135. /**
  136. * Represents an action.
  137. */
  138. private class Action {
  139. private static final int ACTION_OFFSET = 24;
  140. private static final int ACTION_MASK = 0xFF000000;
  141. private static final int FIRST_ARGUMENT_OFFSET = 16;
  142. private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
  143. private static final int SECOND_ARGUMENT_OFFSET = 8;
  144. private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
  145. private static final int THIRD_ARGUMENT_OFFSET = 0;
  146. private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
  147. private int mActionCode;
  148. private int mFirstArgument;
  149. private int mSecondArgument;
  150. private int mThirdArgument;
  151. public void init(String encodedActionString) {
  152. int encodedAction = 0;
  153. try {
  154. // hack
  155. encodedAction = Integer.decode("0x" + encodedActionString);
  156. } catch (NumberFormatException nfe) {
  157. return;
  158. }
  159. mActionCode = (encodedAction & ACTION_MASK) >> ACTION_OFFSET;
  160. mFirstArgument = (encodedAction & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
  161. mSecondArgument = (encodedAction & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
  162. mThirdArgument = (encodedAction & THIRD_ARGUMENT_OFFSET) >> THIRD_ARGUMENT_MASK;
  163. }
  164. }
  165. }