EMMA Coverage Report (generated Tue Jun 26 14:54:12 CEST 2012)
[all classes][org.tomdroid.xml]

COVERAGE SUMMARY FOR SOURCE FILE [NoteContentHandler.java]

nameclass, %method, %block, %line, %
NoteContentHandler.java100% (1/1)100% (4/4)66%  (503/759)65%  (96,2/149)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class NoteContentHandler100% (1/1)100% (4/4)66%  (503/759)65%  (96,2/149)
endElement (String, String, String): void 100% (1/1)53%  (198/376)50%  (29,2/58)
characters (char [], int, int): void 100% (1/1)64%  (95/149)56%  (23/41)
startElement (String, String, String, Attributes): void 100% (1/1)86%  (153/177)82%  (27/33)
NoteContentHandler (SpannableStringBuilder): void 100% (1/1)100% (57/57)100% (17/17)

1/*
2 * Tomdroid
3 * Tomboy on Android
4 * http://www.launchpad.net/tomdroid
5 * 
6 * Copyright 2008, 2009, 2010 Olivier Bilodeau <olivier@bottomlesspit.org>
7 * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org>
8 * 
9 * This file is part of Tomdroid.
10 * 
11 * Tomdroid is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 * 
16 * Tomdroid is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 * GNU General Public License for more details.
20 * 
21 * You should have received a copy of the GNU General Public License
22 * along with Tomdroid.  If not, see <http://www.gnu.org/licenses/>.
23 */
24package org.tomdroid.xml;
25 
26import java.util.ArrayList;
27 
28import org.tomdroid.Note;
29import org.xml.sax.Attributes;
30import org.xml.sax.SAXException;
31import org.xml.sax.helpers.DefaultHandler;
32 
33import android.text.Spannable;
34import android.text.SpannableStringBuilder;
35import android.text.style.BackgroundColorSpan;
36import android.text.style.BulletSpan;
37import android.text.style.LeadingMarginSpan;
38import android.text.style.RelativeSizeSpan;
39import android.text.style.StrikethroughSpan;
40import android.text.style.StyleSpan;
41import android.text.style.TypefaceSpan;
42 
43/*
44 * This class is responsible for parsing the xml note content
45 * and formatting the contents in a SpannableStringBuilder
46 */
47public class NoteContentHandler extends DefaultHandler {
48        
49        // position keepers
50        private boolean inNoteContentTag = false;
51        private boolean inBoldTag = false;
52        private boolean inItalicTag = false;
53        private boolean inStrikeTag = false;
54        private boolean inHighlighTag = false;
55        private boolean inMonospaceTag = false;
56        private boolean inSizeSmallTag = false;
57        private boolean inSizeLargeTag = false;
58        private boolean inSizeHugeTag = false;
59        private int inListLevel = 0;
60        private boolean inListItem = false;
61        
62        // -- Tomboy's notes XML tags names -- 
63        // Style related
64        private final static String NOTE_CONTENT = "note-content";
65        private final static String BOLD = "bold";
66        private final static String ITALIC = "italic";
67        private final static String STRIKETHROUGH = "strikethrough";
68        private final static String HIGHLIGHT = "highlight";
69        private final static String MONOSPACE = "monospace";
70        private final static String SMALL = "size:small";
71        private final static String LARGE = "size:large";
72        private final static String HUGE = "size:huge";
73        // Bullet list-related
74        private final static String LIST = "list";
75        private final static String LIST_ITEM = "list-item";
76        
77        // holding state for tags
78        private int boldStartPos;
79        private int boldEndPos;
80        private int italicStartPos;
81        private int italicEndPos;
82        private int strikethroughStartPos;
83        private int strikethroughEndPos;
84        private int highlightStartPos;
85        private int highlightEndPos;
86        private int monospaceStartPos;
87        private int monospaceEndPos;
88        private int smallStartPos;
89        private int smallEndPos;
90        private int largeStartPos;
91        private int largeEndPos;
92        private int hugeStartPos;
93        private int hugeEndPos;
94        private ArrayList<Integer> listItemStartPos = new ArrayList<Integer>(0);
95        private ArrayList<Integer> listItemEndPos = new ArrayList<Integer>(0);
96        private ArrayList<Boolean> listItemIsEmpty =  new ArrayList<Boolean>(0);
97        
98        // accumulate note-content in this var since it spans multiple xml tags
99        private SpannableStringBuilder ssb;
100        
101        public NoteContentHandler(SpannableStringBuilder noteContent) {
102                
103                this.ssb = noteContent;
104        }
105        
106        @Override
107        public void startElement(String uri, String localName, String name,        Attributes attributes) throws SAXException {
108                
109                if (name.equals(NOTE_CONTENT)) {
110 
111                        // we are under the note-content tag
112                        // we will append all its nested tags so I create a string builder to do that
113                        inNoteContentTag = true;
114                }
115 
116                // if we are in note-content, keep and convert formatting tags
117                // TODO is XML CaSe SeNsItIve? if not change equals to equalsIgnoreCase and apply to endElement()
118                if (inNoteContentTag) {
119                        if (name.equals(BOLD)) {
120                                inBoldTag = true;
121                        } else if (name.equals(ITALIC)) {
122                                inItalicTag = true;
123                        } else if (name.equals(STRIKETHROUGH)) {
124                                inStrikeTag = true;
125                        } else if (name.equals(HIGHLIGHT)) {
126                                inHighlighTag = true;
127                        } else if (name.equals(MONOSPACE)) {
128                                inMonospaceTag = true;
129                        } else if (name.equals(SMALL)) {
130                                inSizeSmallTag = true;
131                        } else if (name.equals(LARGE)) {
132                                inSizeLargeTag = true;
133                        } else if (name.equals(HUGE)) {
134                                inSizeHugeTag = true;
135                        } else if (name.equals(LIST)) {
136                                inListLevel++;
137                        } else if (name.equals(LIST_ITEM)) {
138                                // Book keeping of where the list-items started and where they end.
139                                // we need to do this here because a list-item must always have a start,
140                                // but it doesn't always have any content--so we must assume that a list-item
141                                // is empty until characters() gets called and proves otherwise.
142                                
143                                if (listItemIsEmpty.size() < inListLevel) {
144                                        listItemIsEmpty.add(new Boolean(true));
145                                }
146                                // if listItem's position not already in tracking array, add it.
147                                // Otherwise if the start position equals 0 then set
148                                if (listItemStartPos.size() < inListLevel) {
149                                        listItemStartPos.add(new Integer(ssb.length()));
150                                } else if (listItemStartPos.get(inListLevel-1) == 0) { 
151                                        listItemStartPos.set(inListLevel-1, new Integer(ssb.length()));                                        
152                                }
153                                // no matter what, we track the end (we add if array not big enough or set otherwise) 
154                                if (listItemEndPos.size() < inListLevel) {
155                                        listItemEndPos.add(new Integer(ssb.length()));
156                                } else {
157                                        listItemEndPos.set(inListLevel-1, ssb.length());                                        
158                                }
159                                inListItem = true;
160                        }
161                }
162 
163        }
164 
165        @Override
166        public void endElement(String uri, String localName, String name)
167                        throws SAXException {
168 
169                if (name.equals(NOTE_CONTENT)) {
170                        inNoteContentTag = false;
171                }
172                
173                // if we are in note-content, keep and convert formatting tags
174                if (inNoteContentTag) {
175                        if (name.equals(BOLD)) {
176                                inBoldTag = false;
177                                // apply style and reset position keepers
178                                ssb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), boldStartPos, boldEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
179                                boldStartPos = 0;
180                                boldEndPos = 0;
181 
182                        } else if (name.equals(ITALIC)) {
183                                inItalicTag = false;
184                                // apply style and reset position keepers
185                                ssb.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), italicStartPos, italicEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
186                                italicStartPos = 0;
187                                italicEndPos = 0;
188 
189                        } else if (name.equals(STRIKETHROUGH)) {
190                                inStrikeTag = false;
191                                // apply style and reset position keepers
192                                ssb.setSpan(new StrikethroughSpan(), strikethroughStartPos, strikethroughEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
193                                strikethroughStartPos = 0;
194                                strikethroughEndPos = 0;
195 
196                        } else if (name.equals(HIGHLIGHT)) {
197                                inHighlighTag = false;
198                                // apply style and reset position keepers
199                                ssb.setSpan(new BackgroundColorSpan(Note.NOTE_HIGHLIGHT_COLOR), highlightStartPos, highlightEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
200                                highlightStartPos = 0;
201                                highlightEndPos = 0;
202                                
203                        } else if (name.equals(MONOSPACE)) {
204                                inMonospaceTag = false;
205                                // apply style and reset position keepers
206                                ssb.setSpan(new TypefaceSpan(Note.NOTE_MONOSPACE_TYPEFACE), monospaceStartPos, monospaceEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
207                                monospaceStartPos = 0;
208                                monospaceEndPos = 0;
209 
210                        } else if (name.equals(SMALL)) {
211                                inSizeSmallTag = false;
212                                // apply style and reset position keepers
213                                ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_SMALL_FACTOR), smallStartPos, smallEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
214                                smallStartPos = 0;
215                                smallEndPos = 0;
216                                
217                        } else if (name.equals(LARGE)) {
218                                inSizeLargeTag = false;
219                                // apply style and reset position keepers
220                                ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_LARGE_FACTOR), largeStartPos, largeEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
221                                largeStartPos = 0;
222                                largeEndPos = 0;
223 
224                        } else if (name.equals(HUGE)) {
225                                inSizeHugeTag = false;
226                                // apply style and reset position keepers
227                                ssb.setSpan(new RelativeSizeSpan(Note.NOTE_SIZE_HUGE_FACTOR), hugeStartPos, hugeEndPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
228                                hugeStartPos = 0;
229                                hugeEndPos = 0;
230 
231                        } else if (name.equals(LIST)) {
232                                inListLevel--;
233                        } else if (name.equals(LIST_ITEM)) {
234                                
235                                // if this list item is "empty" then we don't need to try rendering anything.
236                                if (!inListItem && listItemIsEmpty.get(inListLevel-1))
237                                {
238                                        listItemStartPos.set(inListLevel-1, new Integer(0));
239                                        listItemEndPos.set(inListLevel-1, new Integer(0));
240                                        listItemIsEmpty.set(inListLevel-1, new Boolean(true));
241                                        
242                                        return;                                        
243                                }
244                                // here, we apply margin and create a bullet span. Plus, we need to reset position keepers.
245                                // TODO new sexier bullets?
246                                // Show a leading margin that is as wide as the nested level we are in
247                                ssb.setSpan(new LeadingMarginSpan.Standard(30*inListLevel), listItemStartPos.get(inListLevel-1), listItemEndPos.get(inListLevel-1), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
248                                ssb.setSpan(new BulletSpan(), listItemStartPos.get(inListLevel-1), listItemEndPos.get(inListLevel-1), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
249                                listItemStartPos.set(inListLevel-1, new Integer(0));
250                                listItemEndPos.set(inListLevel-1, new Integer(0));
251                                listItemIsEmpty.set(inListLevel-1, new Boolean(true));
252                                
253                                inListItem = false;
254                        }
255                }
256        }
257 
258        @Override
259        public void characters(char[] ch, int start, int length)
260                        throws SAXException {
261                
262                String currentString = new String(ch, start, length);
263 
264                if (inNoteContentTag) {
265                        // while we are in note-content, append
266                        ssb.append(currentString, start, length);
267                        int strLenStart = ssb.length()-length;
268                        int strLenEnd = ssb.length();
269                        
270                        // apply style if required
271                        // TODO I haven't tested nested tags yet
272                        if (inBoldTag) {
273                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
274                                if (boldStartPos == 0) {
275                                        boldStartPos = strLenStart;
276                                }
277                                // no matter what, if we are still in the tag, end is now further
278                                boldEndPos = strLenEnd;
279                        }
280                        if (inItalicTag) {
281                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
282                                if (italicStartPos == 0) {
283                                        italicStartPos = strLenStart;
284                                }
285                                // no matter what, if we are still in the tag, end is now further
286                                italicEndPos = strLenEnd;
287                        }
288                        if (inStrikeTag) {
289                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
290                                if (strikethroughStartPos == 0) {
291                                        strikethroughStartPos = strLenStart;
292                                }
293                                // no matter what, if we are still in the tag, end is now further
294                                strikethroughEndPos = strLenEnd;
295                        }
296                        if (inHighlighTag) {
297                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
298                                if (highlightStartPos == 0) {
299                                        highlightStartPos = strLenStart;
300                                }
301                                // no matter what, if we are still in the tag, end is now further
302                                highlightEndPos = strLenEnd;
303                        }
304                        if (inMonospaceTag) {
305                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
306                                if (monospaceStartPos == 0) {
307                                        monospaceStartPos = strLenStart;
308                                }
309                                // no matter what, if we are still in the tag, end is now further
310                                monospaceEndPos = strLenEnd;
311                        }
312                        if (inSizeSmallTag) {
313                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
314                                if (smallStartPos == 0) {
315                                        smallStartPos = strLenStart;
316                                }
317                                // no matter what, if we are still in the tag, end is now further
318                                smallEndPos = strLenEnd;
319                        }
320                        if (inSizeLargeTag) {
321                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
322                                if (largeStartPos == 0) {
323                                        largeStartPos = strLenStart;
324                                }
325                                // no matter what, if we are still in the tag, end is now further
326                                largeEndPos = strLenEnd;
327                        }
328                        if (inSizeHugeTag) {
329                                // if tag is not equal to 0 then we are already in it: no need to reset it's position again 
330                                if (hugeStartPos == 0) {
331                                        hugeStartPos = strLenStart;
332                                }
333                                // no matter what, if we are still in the tag, end is now further
334                                hugeEndPos = strLenEnd;
335                        }
336                        if (inListItem) {
337                                // this list item is not empty, so we mark it as such. We keep track of this to avoid any
338                                // problems with list items nested like this: <item><item><item>Content!</item></item></item>
339                                listItemIsEmpty.set(inListLevel-1, new Boolean(false));
340                                
341                                // no matter what, if we are still in the tag, end is now further
342                                listItemEndPos.set(inListLevel-1, strLenEnd);                                        
343                        }
344                }
345        }
346}

[all classes][org.tomdroid.xml]
EMMA 0.0.0 (unsupported private build) (C) Vladimir Roubtsov