PageRenderTime 27ms CodeModel.GetById 10ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/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
 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}