View Javadoc
1   /*
2    * #%L
3    * JRst :: Api
4    * %%
5    * Copyright (C) 2004 - 2014 CodeLutin, Chatellier Eric
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  
23  package org.nuiton.jrst.legacy;
24  
25  import static org.nuiton.jrst.legacy.ReStructuredText.BLOCK_QUOTE;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.dom4j.DocumentHelper;
30  import org.dom4j.Element;
31  
32  import java.io.IOException;
33  import java.io.Reader;
34  import java.net.URLEncoder;
35  import java.util.ArrayList;
36  import java.util.LinkedList;
37  import java.util.List;
38  import java.util.regex.Matcher;
39  import java.util.regex.Pattern;
40  
41  /**
42   * Le principe est de positionner la mark du {@link AdvancedReader} lors du
43   * debut d'une methode peek*, puis a la fin de la methode de regarder le nombre
44   * de caractere utilisé pour la methode et de faire un reset.
45   * <p>
46   * Le nombre de caractere utilisé servira pour le remove lorsque l'utilisateur
47   * indiquera qu'il utilise l'element retourné, si l'utilisateur n'appelle pas
48   * remove alors il peut relire autant de fois qu'il veut le meme element, ou
49   * essayer d'en lire un autre.
50   * <p>
51   * Pour mettre en place ce mecanisme le plus simple est d'utiliser les methodes
52   * {@code JRSTLexer#beginPeek()} et {@code JRSTLexer#endPeek()}
53   * 
54   * Created: 28 oct. 06 00:44:20
55   *
56   * @author poussin, letellier
57   * @version $Revision$
58   *
59   * Last update: $Date$
60   * by : $Author$
61   */
62  public class JRSTLexer {
63  
64      /** to use log facility, just put in your code: log.info(\"...\"); */
65      private static Log log = LogFactory.getLog(JRSTLexer.class);
66  
67      public static final String BULLET_CHAR = "*" + "+" + "-"/*
68                                                                   * + "\u2022" +
69                                                                   * "\u2023" +
70                                                                   * "\u2043"
71                                                                   */;
72  
73      public static final String TITLE_CHAR = "-=-~'`^+:!\"#$%&*,./;|?@\\_[\\]{}<>()";
74  
75      public static final String DOCINFO_ITEM = "author|authors|organization|address|contact|version|revision|status|date|copyright";
76  
77      public static final String ADMONITION_PATTERN = "admonition|attention|caution|danger|error|hint|important|note|tip|warning";
78  
79      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
80      // Title Elements
81      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82  
83      public static final String TITLE = "title";
84  
85      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86      // Bibliographic Elements
87      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88  
89      public static final String DOCINFO = "docinfo";
90  
91      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
92      // Decoration Elements
93      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94  
95      public static final String DECORATION = "decoration";
96  
97      public static final String HEADER = "header";
98  
99      public static final String FOOTER = "footer";
100 
101     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102     // Structural Elements
103     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
104 
105     public static final String TRANSITION = "transition";
106 
107     public static final String SIDEBAR = "sidebar";
108 
109     public static final String TOPIC = "topic";
110 
111     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
112     // Body Elements
113     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114 
115     static final public String LITERAL_BLOCK = "literal_block";
116 
117     static final public String PARAGRAPH = "paragraph";
118 
119     static final public String BLANK_LINE = "blankLine";
120     
121     static final public String COMMENT = "comment";
122 
123     static final public String SUBSTITUTION_DEFINITION = "substitution_definition";
124 
125     static final public String BULLET_LIST = "bullet_list";
126 
127     static final public String FIELD_LIST = "field_list";
128 
129     static final public String DEFINITION_LIST = "definition_list";
130 
131     static final public String ENUMERATED_LIST = "enumerated_list";
132 
133     static final public String OPTION_LIST = "option_list";
134 
135     public static final String LINE_BLOCK = "line_block";
136 
137     public static final String LINE = "line";
138 
139     public static final String ATTRIBUTION = "attribution";
140 
141     public static final String DOCTEST_BLOCK = "doctest_block";
142 
143     public static final String ADMONITION = "admonition";
144 
145     public static final String TARGET = "target";
146 
147     public static final String FOOTNOTE = "footnote";
148     
149     public static final String FOOTNOTES = "footnotes";
150     
151     public static final String LEVEL = "level";
152     
153     public static final String TARGETANONYMOUS = "targetAnonymous";
154     
155 
156     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
157     // Table Elements
158     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159 
160     static final public String TABLE = "table";
161 
162     static final public String ROW = "row";
163 
164     static final public String CELL = "cell";
165 
166     static final public String TABLE_HEADER = "header";
167 
168     static final public String TABLE_WIDTH = "width";
169 
170     static final public String ROW_END_HEADER = "endHeader";
171 
172     static final public String CELL_INDEX_START = "indexStart";
173 
174     static final public String CELL_INDEX_END = "indexEnd";
175 
176     static final public String CELL_BEGIN = "begin";
177 
178     static final public String CELL_END = "end";
179     
180     static final public String REMOVE = "remove";
181 
182     static final public String INCLUDE = "include";
183     
184     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
185     // Directive Elements
186     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
187 
188     static final public String DIRECTIVE = "directive";
189 
190     static final public String DIRECTIVE_TYPE = "type";
191 
192     static final public String DIRECTIVE_VALUE = "value";
193     
194     
195     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
196     // Attributs
197     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
198     
199     static final public String AUTONUM = "autoNum";
200     
201     static final public String AUTONUMLABEL = "autoNumLabel";
202     
203     static final public String AUTOSYMBOL = "autoSymbol";
204     
205     static final public String BULLET = "bullet";
206     
207     static final public String CHAR = "char";
208     
209     static final public String ID = "id";
210 
211     static final public String CLASSIFIERS = "classifiers";
212     
213     static final public String DELIMITER = "delimiter";
214     
215     static final public String DELIMITEREXISTE ="delimiterExiste";
216     
217     static final public String ENUMTYPE = "enumtype";
218     
219     static final public String REFURI = "refuri";
220     
221     static final public String OPTION = "option";
222     
223     static final public String LITERAL = "literal";
224     
225     static final public String NAME = "name";
226     
227     static final public String NUM ="num";
228     
229     static final public String OPTIONARGUMENT = "option_argument";
230     
231     static final public String OPTIONSTRING = "option_string";
232     
233     static final public String PREFIX = "prefix";
234     
235     static final public String START = "start";
236     
237     static final public String SUBEXISTE = "subExiste";
238     
239     static final public String SUFFIX = "suffix";
240         
241     static final public String SUBTITLE = "subtitle";
242     
243     static final public String TERM = "term";
244     
245     static final public String TITLEATTR = "title";
246     
247     static final public String XMLSPACE = "xml:space";
248     
249     static final public String TYPE = "type";
250     
251 
252     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
253     
254     protected static final String TRUE = "true";
255     
256     protected static final String FALSE = "false";
257     
258     
259     /**
260      * retient le niveau du titre, pour un titre de type double, on met deux
261      * fois le caratere dans la chaine, sinon on le met une seul fois.
262      * 
263      * <pre>
264      * =====
265      * Super
266      * =====
267      * titre
268      * -----
269      * </pre>
270      * 
271      * donnera dans la liste ["==", "-"]
272      */
273     private List<String> titleLevels;
274 
275     private AdvancedReader in;
276 
277     /**
278      * length of the last element returned (number of char need to this element)
279      */
280     private int elementLength;
281     
282     
283     
284 
285     public JRSTLexer(Reader reader) {
286         titleLevels = new ArrayList<String>();
287         in = new AdvancedReader(reader);
288     }
289 
290     /**
291      * true if no more element to read
292      * 
293      * @return boolean
294      * @throws IOException
295      */
296     public boolean eof() throws IOException {
297         in.mark();
298         in.skipBlankLines();
299         boolean result = in.eof();
300         in.reset();
301         return result;
302     }
303 
304     /**
305      * remove one element from list of element already read
306      * 
307      * @throws IOException
308      */
309     public void remove() throws IOException {
310         in.skip(elementLength);
311     }
312 
313     /**
314      * start peek
315      * 
316      * @throws IOException
317      */
318     private void beginPeek() throws IOException {
319         elementLength = 0;
320         in.mark();
321     }
322 
323     /**
324      * end peek
325      * 
326      * @throws IOException
327      */
328     private void endPeek() throws IOException {
329         elementLength = in.readSinceMark();
330         in.reset();
331     }
332 
333     /**
334      * Read block text, block text have same indentation and is continu (no
335      * blank line)
336      * 
337      * @param minLeftMargin
338      *            min left blank needed to accept to read block
339      * @return String[]
340      * @throws IOException
341      */
342     private String[] readBlock(int minLeftMargin) throws IOException {
343         String[] result = new String[0];
344         String firstLine = in.readLine();
345         if (firstLine != null) {
346             in.unread(firstLine, true);
347             int level = level(firstLine);
348             if (level >= minLeftMargin) {
349                 result = in.readWhile("^\\s{" + level + "}\\S+.*");
350             }
351         }
352 
353         return result;
354     }
355 
356     /**
357      * All lines are joined and left and right spaces are removed during join
358      * 
359      * @param String
360      *            [] lines
361      * @return String
362      */
363     private String joinBlock(String[] lines) {
364         String result = joinBlock(lines, " ", true);
365         return result;
366     }
367 
368     /**
369      * All lines are joined whith the String joinSep and left and right spaces
370      * are removed if trim
371      * 
372      * @param String
373      *            [] lines
374      * @param String
375      *            joinSep
376      * @param Boolean
377      *            trim
378      * @return String
379      */
380     private String joinBlock(String[] lines, String joinSep, boolean trim) {
381         String result = "";
382         String sep = "";
383         for (String line : lines) {
384             if (trim) {
385                 line = line.trim();
386             }
387             result += sep + line;
388             sep = joinSep;
389         }
390         return result;
391     }
392 
393     /**
394      * search if the doc have an header
395      * 
396      * <pre>
397      *    .. header:: This space for rent. aaaa **aaaa**
398      * </pre>
399      * 
400      * @return Element
401      * @throws IOException
402      */
403     public Element peekHeader() throws IOException {
404         beginPeek();
405         Element result = null;
406         String[] line = in.readAll();
407         if (line != null) {
408             int i = 0;
409             for (String l : line) {
410                 i++;
411                 if (l.matches("^\\s*.. " + HEADER + ":: .*")) {
412                     int level = level(l);
413                     l = l.replaceAll("^\\s*.. " + HEADER + ":: ", "");
414                     result = DocumentHelper.createElement(HEADER).addAttribute(
415                             LEVEL, String.valueOf(level));
416                     result.addAttribute(LINE, "" + i);
417                     result.setText(l);
418                 }
419 
420             }
421         }
422         endPeek();
423         return result;
424 
425     }
426 
427     /**
428      * search if the doc have an header
429      * 
430      * <pre>
431      *    .. footer:: design by **LETELLIER Sylvain**
432      * </pre>
433      * 
434      * @return Element
435      * @throws IOException
436      */
437     public Element peekFooter() throws IOException {
438         beginPeek();
439         Element result = null;
440         String[] line = in.readAll();
441         if (line != null) {
442             int i = 0;
443             for (String l : line) {
444                 i++;
445 
446                 if (l.matches("^\\s*.. " + FOOTER + ":: .*")) {
447                     int level = level(l);
448                     l = l.replaceAll("^\\s*.. " + FOOTER + ":: ", "");
449                     result = DocumentHelper.createElement(FOOTER).addAttribute(
450                             LEVEL, String.valueOf(level));
451                     result.addAttribute(LINE, "" + i);
452                     result.setText(l);
453                 }
454             }
455         }
456         endPeek();
457         return result;
458 
459     }
460 
461     /**
462      * <pre>
463      * .. __: http://www.python.org
464      * </pre>
465      * 
466      * @return Element
467      * @throws IOException
468      */
469     public LinkedList<Element> peekTargetAnonymous() throws IOException {
470         beginPeek();
471         LinkedList<Element> result = new LinkedList<Element>();
472         String[] line = in.readAll();
473         if (line != null) {
474             int i = 0;
475             for (String l : line) {
476                 i++;
477 
478                 if (l.matches("^\\s*__ .+$|^\\s*\\.\\. __\\:.+$")) {
479                 	log.debug(l);
480                     Element resultTmp = DocumentHelper
481                             .createElement(TARGETANONYMOUS);
482                     resultTmp.addAttribute(LEVEL, "" + level(l));
483                     Matcher matcher = Pattern.compile("__ |.. __: ").matcher(l);
484                     
485                     if (matcher.find()) {
486                         resultTmp.addAttribute(REFURI, l.substring(matcher
487                                 .end(), l.length()));
488                     }
489                     
490                     result.add(resultTmp);
491                 }
492             }
493 
494         }
495         endPeek();
496         return result;
497     }
498 
499     /**
500      * Return title or para
501      * 
502      * @return Element
503      * @throws IOException
504      */
505     public Element peekTitleOrBodyElement() throws IOException {
506         Element result = null;
507         if (result == null) {
508             result = peekTitle();
509         }
510         if (result == null) {
511             result = peekBodyElement();
512         }
513 
514         return result;
515     }
516 
517     /**
518      * read doc info author, date, version, ... or field list element
519      * 
520      * <pre>
521      * :author: Benjamin Poussin
522      * :address:
523      *   Quelque part
524      *   Dans le monde
525      * </pre>
526      * 
527      * @return Element
528      * @throws IOException
529      */
530     public Element peekDocInfo() throws IOException {
531         Element result = null;
532         if (result == null) {
533             result = peekDocInfoItem();
534 
535         }
536         if (result == null) {
537             result = peekFieldList();
538         }
539 
540         return result;
541 
542     }
543 
544     /**
545      * Return para
546      * 
547      * @return Element
548      * @throws IOException
549      */
550     public Element peekBodyElement() throws IOException {
551         Element result = null;
552         if (result == null) {
553             result = peekInclude();
554         }
555         if (result == null) {
556             result = peekDoctestBlock();
557         }
558         if (result == null) {
559             result = peekAdmonition();
560         }
561         if (result == null) {
562             result = peekSidebar();
563         }
564         if (result == null) {
565             result = peekTopic();
566         }
567         if (result == null) {
568             result = peekRemove();
569         }
570         if (result == null) {
571             result = peekDirectiveOrReference();
572         }
573         if (result == null) {
574             result = peekTransition();
575         }
576         if (result == null) {
577             result = peekTable();
578         }
579         if (result == null) {
580             result = peekLineBlock();
581         }
582         if (result == null) {
583             result = peekBulletList();
584         }
585         if (result == null) {
586             result = peekOption();
587         }
588         if (result == null) {
589             result = peekEnumeratedList();
590         }
591         if (result == null) {
592             result = peekTarget();
593         }
594         if (result == null) {
595             result = peekFootnote();
596         }
597         // comment must be read after peekDirectiveOrReference()
598         // and peekFootnote()
599         if (result == null) {
600             result = peekComment();
601         }
602         if (result == null) {
603             result = peekDefinitionList();
604         }
605         if (result == null) {
606             result = peekFieldList();
607         }
608         if (result == null) {
609             result = peekTargetAnonymousBody();
610         }
611         if (result == null) {
612             result = peekLiteralBlock();
613         }
614         if (result == null) {
615             result = peekBlockQuote();
616         }
617         if (result == null) {
618             result = peekBlankLine();
619         }
620         if (result == null) {
621             result = peekPara();
622         }
623 
624         return result;
625     }
626 
627     /**
628      * Remove already read elements
629      * 
630      * @return Element
631      * @throws IOException
632      */
633     public Element peekRemove() throws IOException {
634         beginPeek();
635         Element result = null;
636         String line = in.readLine();
637         if (line != null) {
638             // Le header est parse des le debut
639             if (line.matches("^\\s*.. " + HEADER + ":: .*")) {
640                 result = DocumentHelper.createElement(REMOVE).addAttribute(
641                         LEVEL, "" + level(line));
642             }
643             // Le footer
644             if (line.matches("^\\s*.. " + FOOTER + ":: .*")) {
645                 result = DocumentHelper.createElement(REMOVE).addAttribute(
646                         LEVEL, "" + level(line));
647             }
648 
649         }
650         endPeek();
651         return result;
652     }
653 
654     /**
655      * read include
656      * 
657      * <pre>
658      * .. include:: text.txt
659      * or
660      * .. include:: literal
661      *      text.txt
662      * 
663      * </pre>
664      * 
665      * @return Element
666      * @throws IOException
667      */
668     private Element peekInclude() throws IOException {
669         beginPeek();
670         Element result = null;
671         String line = in.readLine();
672         if (line != null) {
673             if (line.matches("^\\s*\\.\\.\\sinclude\\:\\:.+$")) {
674                 result = DocumentHelper.createElement(INCLUDE);
675                 result.addAttribute(LEVEL, "" + level(line));
676                 String option = line.substring(line.indexOf("::") + 2).trim();
677                 result.addAttribute(OPTION, "");
678                 if (option.trim().equalsIgnoreCase(LITERAL)) {
679                     result.addAttribute(OPTION, LITERAL);
680                     line = in.readLine();
681                     result.setText(line.trim());
682                 } else {
683                     result.setText(option);
684                 }
685 
686             }
687         }
688         endPeek();
689         return result;
690     }
691 
692     /**
693      * read options
694      * 
695      * <pre>
696      * Ex :     -a            command-line option &quot;a&quot;
697      *          -1 file, --one=file, --two file
698      *                     Multiple options with arguments.
699      * Schéma :  ________________________________
700      *           v          |                    |
701      *        -{1,2}\w+ -&gt;|','                   |
702      *                    |'='-----|-&gt; \w+ ---&gt;|','
703      *                    |' '-----|           |' '---+
704      *                    |&quot;  &quot; -----&gt; \w+ ---&gt; end   |
705      *                      &circ;                         |
706      *                      |_________________________|
707      * Légende :   
708      * 
709      *          -{1,2} --&gt; 1 or 2 tirets
710      *          \w+ -----&gt; word characters one or more times
711      * </pre>
712      * 
713      * @return Element
714      * @throws IOException
715      */
716     public Element peekOption() throws IOException {
717 
718         beginPeek();
719         Element result = null;
720         String line = in.readLine();
721         if (line != null) {
722             if (line.matches("^(\\s*((--?)|(//?))\\w+([ =][<a-zA-Z][\\w-><]*)?)\\s*.*$")) {
723                 result = DocumentHelper.createElement(OPTION_LIST)
724                         .addAttribute(LEVEL, "" + level(line));
725                 char delimiter;
726                 do {
727                     Matcher matcher = Pattern.compile("[-/][-/]?.+").matcher(line);
728                     matcher.find();
729                     Element option = result.addElement(OPTION);
730                     String option_stringTmp = matcher.group();
731                     matcher = Pattern.compile("^[-/][-/]?\\w+").matcher(option_stringTmp);
732                     matcher.find();
733                     String option_string = matcher.group();
734                     option.addAttribute(OPTIONSTRING, option_string);
735                     
736                     boolean done = false;
737                     // Delimiteur bidon
738                     delimiter = '.';
739                     if (option_stringTmp.length() > matcher.end()) {
740                         delimiter = option_stringTmp.charAt(matcher.end());
741                         option_stringTmp = option_stringTmp.substring(
742                             matcher.end(), option_stringTmp.length());
743                     } else {
744                         done = true;
745                     }
746                     option.addAttribute(DELIMITEREXISTE, FALSE);
747 
748                     if (delimiter == ' ') { // S'il y a 2 espaces a suivre,
749                         // l'option est finie
750                         if (option_stringTmp.charAt(1) == ' ') {
751                             done = true;
752                         }
753                     }
754                     String option_argument = null;
755                     if ((delimiter == '=' || delimiter == ' ') && !done) {
756                         option.addAttribute(DELIMITEREXISTE, TRUE);
757                         option.addAttribute(DELIMITER, "" + delimiter);
758                         matcher = Pattern.compile(delimiter + "(([a-zA-Z][\\w-]+)|(<[a-zA-Z][^>]*>))").matcher(
759                                 option_stringTmp);
760                         if (matcher.find()) {
761                             option_argument = matcher.group().substring(1,
762                                     matcher.group().length());
763                             option.addAttribute(OPTIONARGUMENT, option_argument);
764                             int size = option_argument.length() + 1;
765                             if (option_stringTmp.length() < size && option_stringTmp.charAt(size) == ',') {
766                                 delimiter = ',';
767                             } else {
768                                 done = true;
769                             }
770                         } else { // Si la description n'est pas sur la meme
771                             // ligne
772                             option_argument = option_stringTmp;
773                             option.addAttribute(OPTIONARGUMENT,
774                                     option_argument);
775                             line = in.readLine();
776                             if (line != null) {
777                                 result.setText(line.trim());
778                             }
779                         }
780                     }
781                     if (delimiter == ',') {
782                         line = line.substring(option_string.length() + 1
783                                 + (option_argument == null ? 0 : option_argument.length()));
784                     }
785                     if (done) {
786                         result.setText(option_stringTmp.substring(matcher.end(), option_stringTmp.length()).trim()
787                                 + " " + joinBlock(readBlock(1)));
788                     }
789                 } while (delimiter == ',');
790             }
791         }
792         endPeek();
793         return result;
794     }
795 
796     /**
797      * read topic
798      * 
799      * <pre>
800      * -.. topic:: Title
801      *     Body.
802      * </pre>
803      * 
804      * @return Element
805      * @throws IOException
806      */
807     private Element peekTopic() throws IOException {
808         beginPeek();
809         Element result = null;
810         String line = in.readLine();
811         if (line != null) {
812             if (line.matches("^\\.\\.\\s+(" + TOPIC + ")::\\s+(.*)$")) {
813                 Matcher matcher = Pattern.compile(TOPIC + "::").matcher(line);
814                 matcher.find();
815                 result = DocumentHelper.createElement(TOPIC).addAttribute(
816                         LEVEL, "" + level(line));
817                 String title = line.substring(matcher.end(), line.length());
818                 result.addAttribute(TITLE, title);
819                 line = in.readLine();
820                 if (line.matches("\\s*")) {
821                     line = in.readLine();
822                 }
823                 int level = level(line);
824                 String[] lines = null;
825                 if (level != 0) {
826                     lines = in.readWhile("(^ {" + level + "}.*)|(\\s*)");
827                     String txt = line;
828                     for (String txtTmp : lines) {
829                         txt += "\n" + txtTmp.trim();
830                     }
831                     result.setText(txt);
832                 }
833 
834             }
835         }
836 
837         endPeek();
838         return result;
839     }
840 
841     /**
842      * read sidebar
843      * 
844      * <pre>
845      * .. sidebar:: Title
846      *    :subtitle: If Desired
847      *    Body.
848      * </pre>
849      * 
850      * @return Element
851      * @throws IOException
852      */
853     private Element peekSidebar() throws IOException {
854         beginPeek();
855         Element result = null;
856         String line = in.readLine();
857         if (line != null) {
858             if (line.matches("^\\.\\.\\s*(" + SIDEBAR + ")::\\s*(.*)$")) {
859                 Matcher matcher = Pattern.compile(SIDEBAR + "::").matcher(line);
860                 matcher.find();
861                 result = DocumentHelper.createElement(SIDEBAR).addAttribute(
862                         LEVEL, "" + level(line));
863                 String title = line.substring(matcher.end(), line.length());
864                 result.addAttribute(TITLE, title);
865                 line = in.readLine();
866                 if (line.matches("^\\s+:subtitle:\\s*(.*)$*")) {
867                     matcher = Pattern.compile(":subtitle:\\s*").matcher(line);
868                     matcher.find();
869                     String subTitle = line.substring(matcher.end(), line
870                             .length());
871                     result.addAttribute(SUBEXISTE, TRUE);
872                     result.addAttribute(SUBTITLE, subTitle);
873                     line = in.readLine();
874                 } else {
875                     result.addAttribute(SUBEXISTE, FALSE);
876                 }
877                 String txt = joinBlock(readBlock(level(line)));
878                 result.setText(txt);
879 
880             }
881         }
882 
883         endPeek();
884         return result;
885     }
886 
887     /**
888      * read line block
889      * 
890      * <pre>
891      * |        A one, two, a one two three four
892      * |
893      * | Half a bee, philosophically,
894      * |     must, *ipso facto*, half not be.
895      * | But half the bee has got to be,
896      * |               *vis a vis* its entity.  D'you see?
897      * </pre>
898      * 
899      * @return Element
900      * @throws IOException
901      */
902     private Element peekLineBlock() throws IOException {
903         beginPeek();
904         Element result = null;
905         String line = in.readLine();
906         if (line != null) {
907             if (line.matches("\\|\\s.*")) {
908                 String[] linesTmp = readBlock(0);
909                 String[] lines = new String[linesTmp.length + 1];
910                 lines[0] = line;
911                 for (int i = 0; i < linesTmp.length; i++) {
912                     lines[i + 1] = linesTmp[i];
913                 }
914                 int[] levelsTmp = new int[lines.length];
915                 int levelmin = 999;
916                 result = DocumentHelper.createElement(LINE_BLOCK).addAttribute(
917                         LEVEL, 0 + "");
918                 for (int i = 0; i < levelsTmp.length; i++) {
919                     // on enleve |
920                     lines[i] = lines[i].replaceAll("\\|\\s?", "");
921                 }
922                 for (int i = 0; i < levelsTmp.length; i++) {
923                     // determination des levels
924                     levelsTmp[i] = level(lines[i]);
925                 }
926                 for (int i : levelsTmp) {
927                     // level minimal
928                     levelmin = Math.min(levelmin, i);
929                 }
930                 int cnt = 0;
931                 String lineAv = "";
932                 int[] levels = new int[levelsTmp.length];
933                 for (String l : lines) {
934                     if (!l.matches("\\s*")) { // Si la ligne courante n'est
935                         // pas vide
936                         int level = levelsTmp[cnt] - levelmin;
937                         if (level != 0) {
938                             if (cnt != 0) {
939                                 if (!lineAv.matches("\\s*")) { // Si la ligne
940                                     // d'avant n'est
941                                     // pas vide
942                                     int levelAv = levelsTmp[cnt - 1] - levelmin;
943                                     if (levelAv < level) {
944                                         levels[cnt] = levels[cnt - 1] + 1;
945                                         if (cnt != levels.length) {
946                                             int levelAp = levelsTmp[cnt + 1]
947                                                     - levelmin;
948                                             if (levelAp < level
949                                                     && levelAv < levelAp) {
950                                                 levels[cnt]++;
951                                             }
952                                         }
953                                     } else {
954                                         levels[cnt] = levels[cnt - 1] - 1;
955                                     }
956                                 } else {
957                                     levels[cnt] = 1;
958                                 }
959                             } else {
960                                 levels[cnt] = 1;
961                             }
962                         } else {
963                             levels[cnt] = 0;
964                         }
965                     } else {
966                         if (cnt != 0) {
967                             levels[cnt] = levels[cnt - 1];
968                         }
969                         else {
970                             levels[cnt] = 0;
971                         }
972                     }
973                     cnt++;
974                     lineAv = l;
975                 }
976                 for (int i = 0; i < levels.length; i++) {
977                     Element eLine = result.addElement(LINE);
978                     eLine.addAttribute(LEVEL, "" + levels[i]);
979                     eLine.setText(lines[i].trim());
980                 }
981             }
982         }
983         endPeek();
984         return result;
985     }
986 
987     /**
988      * read doctest block
989      * 
990      * <pre>
991      * &gt;&gt;&gt; print 'this is a Doctest block'
992      * this is a Doctest block
993      * </pre>
994      * 
995      * @return Element
996      * @throws IOException
997      */
998     private Element peekDoctestBlock() throws IOException {
999         beginPeek();
1000         Element result = null;
1001         String line = in.readLine();
1002         if (line != null) {
1003             if (line.matches("^\\s*>>>\\s.*")) {
1004                 int level = level(line);
1005                 result = DocumentHelper.createElement(DOCTEST_BLOCK)
1006                         .addAttribute(LEVEL, String.valueOf(level));
1007                 result.addAttribute(XMLSPACE, "preserve");
1008                 line += "\n" + joinBlock(readBlock(level));
1009                 result.setText(line);
1010             }
1011         }
1012         endPeek();
1013         return result;
1014     }
1015 
1016     /**
1017      * read block quote
1018      * 
1019      * <pre>
1020      * As a great paleontologist once said,
1021      * 
1022      *     This theory, that is mine, is mine.
1023      * 
1024      *     -- Anne Elk (Miss)
1025      * </pre>
1026      * 
1027      * @return Element
1028      * @throws IOException
1029      */
1030     private Element peekBlockQuote() throws IOException {
1031         beginPeek();
1032         Element result = null;
1033         String line = in.readLine();
1034         if (line != null) {
1035             if (line.matches("\\s.*")) {
1036                 int level = level(line);
1037                 String savedLine = line + " " + joinBlock(readBlock(level));
1038                 line = in.readLine();
1039 
1040                 if (line != null) {
1041                     level = level(line);
1042                     String blockQuote = null;
1043                     if (level != 0) {
1044                         String txt = line;
1045                         String[] lines = in.readWhile("(^ {" + level
1046                                 + "}.*)|(\\s*)");
1047                         for (String l : lines) {
1048                             if (l.matches("^ {" + level + "}--\\s*.*")) {
1049                                 blockQuote = l;
1050                                 blockQuote = blockQuote.replaceAll("--", "")
1051                                         .trim();
1052                             } else {
1053                                 txt += "\n" + l;
1054                             }
1055                         }
1056                         result = DocumentHelper.createElement(BLOCK_QUOTE)
1057                                 .addAttribute(LEVEL, String.valueOf(level));
1058                         if (blockQuote != null) {
1059                             result.addAttribute(ATTRIBUTION, blockQuote);
1060                         }
1061                         result.setText(savedLine + txt);
1062                     }
1063                 }
1064             }
1065         }
1066         endPeek();
1067         return result;
1068     }
1069 
1070     /**
1071      * read admonitions :
1072      * admonition|attention|caution|danger|error|hint|important|note|tip|warning
1073      * 
1074      * <pre>
1075      * .. Attention:: All your base are belong to us.
1076      * .. admonition:: And, by the way...
1077      * 
1078      *    You can make up your own admonition too.
1079      * </pre>
1080      * 
1081      * @return Element
1082      * @throws IOException
1083      */
1084     protected Element peekAdmonition() throws IOException {
1085         beginPeek();
1086         Element result = null;
1087         String line = in.readLine();
1088         if (line != null) {
1089             String lineTest = line.toLowerCase();
1090             Pattern pAdmonition = Pattern.compile("^\\s*\\.\\.\\s("
1091                     + ADMONITION_PATTERN + ")::\\s*(.*)$");
1092             Matcher matcher = pAdmonition.matcher(lineTest);
1093 
1094             if (matcher.matches()) {
1095 
1096                 boolean admonition = false;
1097                 matcher = Pattern.compile(ADMONITION_PATTERN).matcher(lineTest);
1098                 matcher.find();
1099                 int level = level(line);
1100                 result = DocumentHelper.createElement(ADMONITION).addAttribute(
1101                         LEVEL, "" + level);
1102 
1103                 if (matcher.group().equals(ADMONITION)) { // Il y a un titre
1104                     // pour un
1105                     // admonition
1106                     // general
1107                     admonition = true;
1108                     result.addAttribute(TYPE, ADMONITION);
1109                     String title = line.substring(matcher.end() + 2, line
1110                             .length());
1111 
1112                     result.addAttribute(TITLEATTR, title);
1113                 } else {
1114                     result.addAttribute(TYPE, matcher.group());
1115                 }
1116                 
1117                 String firstLine = "";
1118                 if (!admonition && matcher.end() + 2 < line.length()) {
1119                     firstLine = line
1120                             .substring(matcher.end() + 2, line.length());
1121                 }
1122                 line = in.readLine();
1123                 if (line != null) {
1124                     if (line.matches("\\s*")) {
1125                         line = in.readLine();
1126                     }
1127                     if (line != null && !line.matches("\\s*")) {
1128                         level = level(line);
1129                         String txt = firstLine.trim() + "\n" + line + "\n";
1130                         txt += "\n" + readBlockWithBlankLine(level);
1131                         result.setText(txt);
1132                     } else {
1133                         result.setText(firstLine);
1134                     }
1135                 } else {
1136                     result.setText(firstLine);
1137                 }
1138             }
1139         }
1140         endPeek();
1141         return result;
1142     }
1143 
1144     /**
1145      * read blank line
1146      * 
1147      * @return Element
1148      * @throws IOException
1149      */
1150     public Element peekBlankLine() throws IOException {
1151         beginPeek();
1152         Element result = null;
1153 
1154         // must have one blank line before
1155         String line = in.readLine();
1156         if (line != null && line.matches("\\s*")) {
1157             int level = level(line);
1158             result = DocumentHelper.createElement(BLANK_LINE).addAttribute(
1159                     LEVEL, String.valueOf(level));
1160         }
1161 
1162         endPeek();
1163         return result;
1164     }
1165 
1166     /**
1167      * read directive or reference
1168      * 
1169      * @return Element
1170      * @throws IOException
1171      */
1172     public Element peekDirectiveOrReference() throws IOException {
1173         beginPeek();
1174         Element result = null;
1175         String line = in.readLine();
1176         if (line != null) {
1177             Pattern pImage = Pattern
1178                     .compile("^\\.\\.\\s*(?:\\|([^|]+)\\|)?\\s*(\\w+)::\\s*(.*)$");
1179             Matcher matcher = pImage.matcher(line);
1180             if (matcher.matches()) {
1181                 String ref = matcher.group(1);
1182                 String directiveType = matcher.group(2);
1183                 String directiveValue = matcher.group(3);
1184                 Element directive = null;
1185                 if (ref != null && !"".equals(ref)) {
1186                     result = DocumentHelper
1187                             .createElement(SUBSTITUTION_DEFINITION);
1188                     result.addAttribute(NAME, ref);
1189                     directive = result.addElement(DIRECTIVE);
1190                 } else {
1191                     result = DocumentHelper.createElement(DIRECTIVE);
1192                     directive = result;
1193                 }
1194                 result.addAttribute(LEVEL, "0");
1195 
1196                 directive.addAttribute(DIRECTIVE_TYPE, directiveType);
1197                 directive.addAttribute(DIRECTIVE_VALUE, directiveValue);
1198 
1199                 String[] lines = readBlock(1);
1200                 String text = joinBlock(lines, "\n", false);
1201 
1202                 directive.setText(text);
1203             }
1204         }
1205         endPeek();
1206         return result;
1207     }
1208 
1209     /**
1210      * read transition
1211      * 
1212      * @return Element
1213      * @throws IOException
1214      */
1215     public Element peekTransition() throws IOException {
1216         beginPeek();
1217 
1218         Element result = null;
1219         // no eat blank line, see next comment
1220 
1221         // must have one blank line before
1222         String line = in.readLine();
1223         if (line != null && line.matches("\\s*")) {
1224             // in.skipBlankLines();
1225             line = in.readLine();
1226             if (line != null && line.matches("-{3,}\\s*")) {
1227                 line = in.readLine();
1228                 // must have one blank line after
1229                 if (line != null && line.matches("\\s*")) {
1230                     result = DocumentHelper.createElement(TRANSITION)
1231                             .addAttribute(LEVEL, String.valueOf(0));
1232                 }
1233             }
1234         }
1235         endPeek();
1236         return result;
1237     }
1238 
1239     /**
1240      * read paragraph with attribut level that represente the space numbers at
1241      * left side
1242      * 
1243      * @return &lt;paragraph level="[int]"&gt[text]&lt;/paragraph&gt;
1244      * @throws IOException
1245      */
1246     public Element peekPara() throws IOException {
1247         beginPeek();
1248 
1249         Element result = null;
1250         // in.skipBlankLines();
1251 
1252         String[] lines;
1253         do {
1254             lines = readBlock(0);
1255             if (lines.length > 0) {
1256                 int level = level(lines[0]);
1257                 String para = joinBlock(lines);
1258 
1259                 boolean literal = false;
1260                 if (para.endsWith(": ::")) {
1261                     para = para.substring(0, para.length() - " ::".length());
1262 
1263                     in.unread("::", true);
1264                     for(int i=0;i<level;i++) {                    
1265                         in.add(' ');
1266                     }
1267                     
1268                     literal = true;
1269                 } else if (para.endsWith("::")) {
1270                     para = para.substring(0, para.length() - ":".length()); // keep
1271                     // one
1272                     // :
1273                     
1274                     in.unread("::", true);
1275                     
1276                     for(int i=0;i<level;i++) {                    
1277                         in.add(' ');
1278                     }
1279                     
1280                     literal = true;
1281                 }
1282 
1283                 if (para.length() == 0 || ":".equals(para)) {
1284                     if (literal) {
1285                         in.readLine(); // eat "::"
1286                     }
1287                 } else {
1288                     // if para is empty, there are error and possible
1289                     // infiny loop on para, force read next line
1290                     result = DocumentHelper.createElement(PARAGRAPH)
1291                             .addAttribute(LEVEL, String.valueOf(level))
1292                             .addText(para);
1293                 }
1294             }
1295         } while (result == null && lines.length > 0);
1296 
1297         endPeek();
1298         return result;
1299     }
1300 
1301     /**
1302      * read literal block
1303      * 
1304      * <pre>
1305      * ::
1306      * 
1307      *     LiteralBlock
1308      * </pre>
1309      * 
1310      * @return Element
1311      * @throws IOException
1312      */
1313     public Element peekLiteralBlock() throws IOException {
1314         beginPeek();
1315 
1316         Element result = null;
1317         // in.skipBlankLines();
1318         
1319         
1320         String[] prefix = in.readLines(2);
1321         if (prefix.length == 2 && prefix[0].matches("\\s*::\\s*")
1322                 && prefix[1].matches("\\s*")) {
1323             
1324             int level = level(prefix[0]);
1325 
1326             String para = in.readLine();
1327             if (para != null) {
1328                 level=level+1;
1329                 para = para.substring(level) + "\n";
1330 
1331                 // it's literal block until level is down
1332                 String[] lines = in.readWhile("(^ {" + level + "}.*|\\s*)");
1333                 while (lines.length > 0) {
1334                     for (String line : lines) {
1335                         if (!line.matches("\\s*")) {
1336                             para += line.substring(level) + "\n";
1337                         }
1338                         else {
1339                             para += "\n";
1340                         }
1341                     }
1342                     lines = in.readWhile("(^ {" + level + "}.*|\\s*)");
1343                 }
1344 
1345                 result = DocumentHelper.createElement(LITERAL_BLOCK)
1346                         .addAttribute(LEVEL, String.valueOf(level)).addText(
1347                                 para);
1348             }
1349         }
1350 
1351         endPeek();
1352         return result;
1353     }
1354 
1355     /**
1356      * read doc info author, date, version, ...
1357      * 
1358      * <pre>
1359      * :author: Benjamin Poussin
1360      * :address:
1361      *   Quelque part
1362      *   Dans le monde
1363      * </pre>
1364      * 
1365      * @return Element
1366      * @throws IOException
1367      */
1368     public Element peekDocInfoItem() throws IOException {
1369         beginPeek();
1370 
1371         Element result = null;
1372         // in.skipBlankLines();
1373         String line = in.readLine();
1374         // (?i) case inensitive on docinfo item
1375         if (line != null && line.matches("^:((?i)" + DOCINFO_ITEM + "):.*$")) {
1376 
1377             result = DocumentHelper.createElement(DOCINFO);
1378             result.addAttribute(LEVEL, "0");
1379             String infotype = line.substring(1, line.indexOf(":", 1));
1380 
1381             /*
1382              * if (!in.eof()) { String [] content = readBlock(1); line +=
1383              * joinBlock(content); }
1384              */
1385             String text = line.substring(line.indexOf(":", 1) + 1).trim();
1386             String[] textTmp = in.readWhile("^\\s+.*");
1387             if (textTmp.length != 0) {
1388                 in.mark();
1389             }
1390 
1391             for (String txt : textTmp) {
1392                 text += "\n" + txt.trim();
1393             }
1394 
1395             // CVS, RCS support
1396             text = text.replaceAll("\\$\\w+: (.+?)\\$", "$1");
1397 
1398             result.addAttribute(TYPE, infotype).addText(text);
1399         }
1400         endPeek();
1401         return result;
1402     }
1403 
1404     /**
1405      * read table simple and complexe
1406      * 
1407      * <pre>
1408      * +------------------------+------------+----------+----------+
1409      * | Header row, column 1   | Header 2   | Header 3 | Header 4 |
1410      * | (header rows optional) |            |          |          |
1411      * +========================+============+==========+==========+
1412      * | body row 1, column 1   | column 2   | column 3 | column 4 |
1413      * +------------------------+------------+----------+----------+
1414      * | body row 2             | Cells may span columns.          |
1415      * +------------------------+------------+---------------------+
1416      * </pre>
1417      * 
1418      * @return Element
1419      * @throws IOException
1420      */
1421     @SuppressWarnings("unchecked")
1422     public Element peekTable() throws IOException {
1423         beginPeek();
1424 
1425         Element result = null;
1426         // in.skipBlankLines();
1427         String line = in.readLine();
1428 
1429         if (line != null) {
1430             Pattern pTableBegin = Pattern.compile("^\\s*(\\+-+)+\\+\\s*$");
1431             Matcher matcher = null;
1432 
1433             matcher = pTableBegin.matcher(line);
1434             if (matcher.matches()) { // complexe table
1435                 result = DocumentHelper.createElement(TABLE);
1436                 result.addAttribute(TABLE_HEADER, FALSE);
1437                 int level = level(line);
1438                 result.addAttribute(LEVEL, String.valueOf(level));
1439                 line = line.trim();
1440                 int tableWidth = line.length();
1441                 result.addAttribute(TABLE_WIDTH, String.valueOf(tableWidth));
1442 
1443                 Pattern pCellEnd = Pattern
1444                         .compile("^\\s{"
1445                                 + level
1446                                 + "}(\\+-+\\+|\\|(?:[^+]+))([^+]+(?:\\+|\\|\\s*$)|-+\\+)*\\s*"); // fin
1447                 // de
1448                 // ligne
1449                 Pattern pCell = Pattern.compile("^\\s{" + level
1450                         + "}(\\|[^|]+)+\\|\\s*$"); // une ligne
1451                 Pattern pHeader = Pattern.compile("^\\s{" + level
1452                         + "}(\\+=+)+\\+\\s*$"); // fin du header
1453                 Pattern pEnd = Pattern.compile("^\\s{" + level
1454                         + "}(\\+-+)+\\+\\s*$"); // fin de table
1455 
1456                 // used to know if | is cell separator or not
1457                 String lastSeparationLine = line;
1458                 String lastLine = line;
1459 
1460                 Element row = DocumentHelper.createElement(ROW);
1461                 String[] table = in.readUntilBlank();
1462 
1463                 boolean done = false;
1464                 for (String l : table) {
1465                     done = false;
1466                     l = l.trim();
1467                     if (l.length() != tableWidth) {
1468                         // Erreur dans la table, peut-etre lever une exception ?
1469                         result = null;
1470                         break;
1471                     }
1472                     matcher = pEnd.matcher(l);
1473                     if (!done && matcher.matches()) {
1474                         // fin normale de ligne, on peut directement l'assigner
1475                         lastSeparationLine = l;
1476                         for (Element cell : (List<Element>) row.elements()) {
1477                             cell.addAttribute(CELL_END, TRUE);
1478                         }
1479                         row.addAttribute(ROW_END_HEADER, FALSE);
1480                         result.add(row);
1481                         row = DocumentHelper.createElement(ROW);
1482                         done = true;
1483                     }
1484                     matcher = pHeader.matcher(l);
1485                     if (!done && matcher.matches()) {
1486                         // fin de du header, on peut directement l'assigner
1487                         lastSeparationLine = l;
1488                         for (Element cell : (List<Element>) row.elements()) {
1489                             cell.addAttribute(CELL_END, TRUE);
1490                         }
1491                         row.addAttribute(ROW_END_HEADER, TRUE);
1492                         result.add(row);
1493                         result.addAttribute(TABLE_HEADER, TRUE);
1494                         row = DocumentHelper.createElement(ROW);
1495                         done = true;
1496                     }
1497                     matcher = pCell.matcher(l);
1498                     if (!done && matcher.matches()) {
1499                         // debug
1500                         row.addAttribute("debug", "pCell");
1501                         // recuperation des textes des cellules
1502                         int start = -1;
1503                         String content = "";
1504                         matcher = Pattern.compile("([^|]+)\\|").matcher(l);
1505                         for (int cellNumber = 0; matcher.find(); cellNumber++) {
1506                             int tmpstart = matcher.start(1);
1507                             int end = matcher.end(1);
1508                             String tmpcontent = matcher.group(1);
1509                             // on a forcement un | ou un + au dessus du +
1510                             // et forcement un + sur lastSeparationLine
1511                             // sinon ca veut dire qu'il y avait un | dans la
1512                             // cell
1513                             if ((lastLine.charAt(end) == '|' || lastLine
1514                                     .charAt(end) == '+')
1515                                     && lastSeparationLine.charAt(end) == '+') {
1516                                 if ("".equals(content)) {
1517                                     content = tmpcontent;
1518                                 }
1519                                 else {
1520                                     content += tmpcontent;
1521                                 }
1522                                 if (start == -1) {
1523                                     start = tmpstart;
1524                                 }
1525                                 Element cell = null;
1526                                 if (row.nodeCount() <= cellNumber) {
1527                                     cell = row.addElement(CELL);
1528                                     cell.addAttribute(CELL_END, FALSE);
1529                                 } else {
1530                                     cell = (Element) row.node(cellNumber);
1531                                 }
1532                                 cell.addAttribute(CELL_INDEX_START, String
1533                                         .valueOf(start));
1534                                 cell.addAttribute(CELL_INDEX_END, String
1535                                         .valueOf(end));
1536                                 cell.setText(cell.getText() + content + "\n");
1537                                 start = end + 1; // +1 to pass + or | at end
1538                                 // of cell
1539                                 content = "";
1540                             } else {
1541                                 // start = tmpstart;
1542                                 if (start == -1) {
1543                                     start = tmpstart;
1544                                 }
1545                                 content += tmpcontent + "|";
1546                                 cellNumber--;
1547                             }
1548                         }
1549                         done = true;
1550                     }
1551                     matcher = pCellEnd.matcher(l);
1552                     if (!done && matcher.matches()) {
1553                         // debug
1554                         row.addAttribute("debug", "pCellEnd");
1555                         // fin d'une ligne, on ne peut pas l'assigner
1556                         // directement
1557                         // pour chaque continuation de cellule, il faut copier
1558                         // l'ancienne valeur
1559 
1560                         // mais on commence tout de meme par fermer tout les
1561                         // cells
1562                         for (Element cell : (List<Element>) row.elements()) {
1563                             cell.addAttribute(CELL_END, TRUE);
1564                         }
1565 
1566                         StringBuffer tmp = new StringBuffer(l);
1567                         int start = -1;
1568                         String content = "";
1569                         matcher = Pattern.compile("([^+|]+|-+)([+|])").matcher(
1570                                 l);
1571                         for (int cellNumber = 0; matcher.find(); cellNumber++) {
1572                             int tmpstart = matcher.start(1);
1573                             int end = matcher.end(1);
1574                             String tmpcontent = matcher.group(1);
1575                             String ender = matcher.group(2);
1576                             if (!tmpcontent.matches("-+")) {
1577                                 // on a forcement un | au dessus du + ou du |
1578                                 // sinon ca veut dire qu'il y avait un + dans la
1579                                 // cell
1580                                 if (lastLine.charAt(end) == '|') {
1581                                     if (start == -1) {
1582                                         start = tmpstart;
1583                                     }
1584                                     // -1 and +1 to take the + or | at begin and
1585                                     // end
1586                                     String old = lastSeparationLine.substring(
1587                                             start - 1, end + 1);
1588                                     tmp.replace(start - 1, end + 1, old);
1589                                     if ("".equals(content)) {
1590                                         content = tmpcontent;
1591                                     }
1592                                     Element cell = null;
1593                                     if (row.nodeCount() <= cellNumber) {
1594                                         cell = row.addElement(CELL);
1595                                     } else {
1596                                         cell = (Element) row.node(cellNumber);
1597 
1598                                     }
1599                                     cell.setText(cell.getText() + content
1600                                             + "\n");
1601                                     // on a ajouter des choses dans la cell,
1602                                     // donc
1603                                     // ce n'est pas la fin
1604                                     cell.addAttribute(CELL_END, FALSE);
1605                                     cell.addAttribute(CELL_INDEX_START, String
1606                                             .valueOf(start));
1607                                     cell.addAttribute(CELL_INDEX_END, String
1608                                             .valueOf(end));
1609                                     start = end + 1; // +1 to pass + or | at
1610                                     // end of cell
1611                                     content = "";
1612                                 } else {
1613                                     // start = tmpstart;
1614                                     content += tmpcontent + ender;
1615                                 }
1616                             }
1617                         }
1618                         lastSeparationLine = tmp.toString();
1619                         row.addAttribute(ROW_END_HEADER, FALSE);
1620                         result.add(row);
1621                         row = DocumentHelper.createElement(ROW);
1622                         done = true;
1623                     }
1624                     if (!done) {
1625                         log.warn("Bad table format line " + in.getLineNumber());
1626                     }
1627                     lastLine = l;
1628                 }
1629 
1630                 //
1631                 // line += "\n" + joinBlock(table, "\n", false);
1632                 //
1633                 // result.addText(line);
1634             } else if (line.matches("^\\s*(=+ +)+=+\\s*$")) {
1635                 // Les donnees de la table peuvent depasser de celle-ci
1636                 /*
1637                  * ===== ===== ====== Inputs Output ------------ ------ A B A or
1638                  * B ===== ===== ====== False False Second column of row 1. True
1639                  * False Second column of row 2.
1640                  * 
1641                  * True 2 - Second column of row 3.
1642                  * 
1643                  * - Second item in bullet list (row 3, column 2). ============
1644                  * ======
1645                  */
1646 
1647                 result = DocumentHelper.createElement(TABLE);
1648                 line = line.trim();
1649                 Pattern pBordersEquals = Pattern.compile("^\\s*(=+ +)+=+\\s*$"); // Separation
1650                 // =
1651                 Pattern pBordersTiret = Pattern.compile("^\\s*(-+ +)+-+\\s*$"); // Separation
1652                 // -
1653                 Pattern pBorders = Pattern.compile("^\\s*([=-]+ +)+[=-]+\\s*$"); // =
1654                 // ou
1655                 // -
1656                 String[] table = in.readUntilBlank(); // Recuperation de la
1657                 // table
1658 
1659                 int tableWidth = line.length();
1660                 int nbSeparations = 0;
1661                 for (String l : table) {
1662                     if (l.length() > tableWidth) {
1663                         tableWidth = l.length(); // Determination de la
1664                     } // Determination de la
1665                     // longueur max
1666                     matcher = pBordersEquals.matcher(l);
1667                     if (matcher.matches()) {
1668                         nbSeparations++;
1669                     }
1670 
1671                 }
1672                 // Header if the table contains 3 equals separations
1673                 result.addAttribute(TABLE_HEADER, "" + (nbSeparations == 2));
1674                 int level = level(line);
1675                 result.addAttribute(LEVEL, String.valueOf(level));
1676                 result
1677                         .addAttribute(TABLE_WIDTH, String
1678                                 .valueOf(tableWidth + 1));
1679                 Element row = DocumentHelper.createElement(ROW);
1680                 // Determination of the columns positions
1681                 List<Integer> columns = new LinkedList<Integer>();
1682                 matcher = Pattern.compile("=+\\s+").matcher(line);
1683                 for (int cellNumber = 0; matcher.find(); cellNumber++) {
1684                     columns.add(matcher.end());
1685                 }
1686                 columns.add(tableWidth);
1687 
1688                 // Traitement du tbl
1689                 /*
1690                  * ===== ===== ====== Inputs Output ------------ ------ A B A or
1691                  * B ===== ===== ====== False False Second column of row 1. True
1692                  * False Second column of row 2.
1693                  * 
1694                  * True 2 - Second column of row 3.
1695                  * 
1696                  * - Second item in bullet list (row 3, column 2). ============
1697                  * ====== devient l'equivalent : ===== ===== ====== Inputs
1698                  * Output ------------ ------ A B A or B ===== ===== ======
1699                  * False False Second column of row 1. ----- ----- ------ True
1700                  * False Second column of row 2. ----- ----- ------ True 2 -
1701                  * Second column of row 3. - Second item in bullet list (row 3,
1702                  * column 2). ============ ======
1703                  */
1704                 String lineRef = line.replace('=', '-');
1705                 Matcher matcher2;
1706                 List<String> tableTmp = new LinkedList<String>();
1707 
1708                 for (int i = 0; i < table.length - 1; i++) {
1709                     tableTmp.add(table[i]);
1710                     if (!table[i].equals("")) {
1711                         if (!table[i + 1]
1712                                 .substring(0, columns.get(0))
1713                                 .matches("\\s*")) {
1714                             matcher = pBorders.matcher(table[i]);
1715                             matcher2 = pBorders.matcher(table[i + 1]);
1716                             if (!matcher.matches() && !matcher2.matches()
1717                                     && !table[i + 1].equals("")) {
1718                                 tableTmp.add(lineRef);
1719                             }
1720                         }
1721                     }
1722                 }
1723                 tableTmp.add(table[table.length - 1]);
1724                 table = new String[tableTmp.size()];
1725                 for (int i = 0; i < tableTmp.size(); i++) {
1726                     table[i] = tableTmp.get(i);
1727                 }
1728 
1729                 boolean done = false;
1730                 LinkedList<String> lastLines = new LinkedList<String>();
1731                 int separation = 1;
1732                 for (String l : table) {
1733                     if (l != null) {
1734                         done = false;
1735                         matcher = pBordersTiret.matcher(l);
1736                         matcher2 = pBordersEquals.matcher(l);
1737                         if (matcher.matches() || matcher2.matches()) { // Intermediate
1738                             // separation
1739                             while (!lastLines.isEmpty()) {
1740                                 matcher = Pattern.compile("[-=]+\\s*").matcher(
1741                                         l);
1742                                 String tmpLine = lastLines.getLast();
1743                                 lastLines.removeLast();
1744                                 int cellNumber;
1745                                 for (cellNumber = 0; matcher.find(); cellNumber++) {
1746                                     Element cell = null;
1747                                     if (row.nodeCount() <= cellNumber) {
1748                                         cell = row.addElement(CELL);
1749                                     } else {
1750                                         cell = (Element) row.node(cellNumber);
1751                                     }
1752                                     if (matcher.start() < tmpLine.length()) {
1753                                         if (columns.size() - 1 == cellNumber) {
1754                                             cell.setText(tmpLine.substring(
1755                                                     matcher.start(), tmpLine
1756                                                             .length())
1757                                                     + "\n");
1758                                         } else {
1759                                             if (matcher.end() < tmpLine
1760                                                     .length()) {
1761                                                 cell.setText(tmpLine.substring(matcher.start(), matcher.end()) + "\n");
1762                                             }
1763                                             else {
1764                                                 cell.setText(tmpLine.substring(matcher.start(), tmpLine.length()) + "\n");
1765                                             }
1766                                         }
1767                                     }
1768 
1769                                     if (lastLines.size() == 0) {
1770                                         row.addAttribute("debug", "pCell");
1771                                         cell.addAttribute(CELL_END, TRUE);
1772                                     } else {
1773                                         row.addAttribute("debug", "pCellEnd");
1774                                         cell.addAttribute(CELL_END, FALSE);
1775                                     }
1776                                     cell.addAttribute(CELL_INDEX_START, String
1777                                             .valueOf(matcher.start() + 1));
1778                                     if (line.length() == matcher.end()) {
1779                                         cell.addAttribute(CELL_INDEX_END, String.valueOf(columns.get(columns.size() - 1)));
1780                                     }
1781                                     else {
1782                                         cell.addAttribute(CELL_INDEX_END, String.valueOf(matcher.end()));
1783                                     }
1784                                 }
1785 
1786                                 if (matcher2.matches()) {
1787                                     separation++;
1788                                     row.addAttribute(ROW_END_HEADER, ""
1789                                             + (separation == 2));
1790                                 } else {
1791                                     row.addAttribute(ROW_END_HEADER, FALSE);
1792                                 }
1793 
1794                                 result.add(row);
1795                                 row = DocumentHelper.createElement(ROW);
1796                                 done = true;
1797                             }
1798                         }
1799                         if (!done && l.matches("^\\s*(.+ +)+.+\\s*$")) {
1800                             // Data
1801                             lastLines.addFirst(l); // Les donnees sont stoquee
1802                             // dans une file d'attente
1803                             // lastLines (FIFO)
1804                             done = true;
1805                         }
1806                         if (!done) {
1807                             log.warn("Bad table format line "
1808                                     + in.getLineNumber());
1809                         }
1810                     }
1811                 }
1812             }
1813         }
1814         endPeek();
1815 
1816         return result;
1817     }
1818 
1819     /**
1820      * read list
1821      * 
1822      * <pre>
1823      * - first line
1824      * - next line
1825      * </pre>
1826      * 
1827      * @return &lt;bullet_list level="[int]"
1828      *         bullet="char"&gt;&lt;[text];&lt;/bullet_list&gt;
1829      * @throws IOException
1830      */
1831     public Element peekBulletList() throws IOException {
1832         beginPeek();
1833 
1834         Element result = null;
1835         // in.skipBlankLines();
1836         String line = in.readLine();
1837         if (line != null
1838                 && line.matches("^\\s*[" + escapeRegex(BULLET_CHAR)
1839                         + "] +\\S.*")) {
1840             int level = level(line);
1841             String bullet = line.substring(level, level + 1);
1842 
1843             result = DocumentHelper.createElement(BULLET_LIST).addAttribute(
1844                     LEVEL, String.valueOf(level)).addAttribute(BULLET,
1845                     bullet);
1846 
1847             if (!in.eof()) {
1848                 String[] content = readBlock(level + 1);
1849                 line += " " + joinBlock(content);
1850             }
1851             String text = line.substring(level + 1).trim();
1852 
1853             result.addText(text);
1854             in.skipBlankLines();
1855         }
1856 
1857         endPeek();
1858         return result;
1859     }
1860 
1861     /**
1862      * read field list
1863      * 
1864      * <pre>
1865      * :first: text
1866      * :second: text
1867      *   and other text
1868      * :last empty:
1869      * </pre>
1870      * 
1871      * @return &lt;field_list level="[int]"
1872      *         name="[text]"&gt;[text]&lt;/field_list&gt;
1873      * @throws IOException
1874      */
1875     public Element peekFieldList() throws IOException {
1876         beginPeek();
1877 
1878         Element result = null;
1879         // in.skipBlankLines();
1880         String line = in.readLine();
1881         if (line != null) {
1882             Pattern pattern = Pattern.compile("^\\s*:([^:]+): [^\\s].*");
1883             Matcher matcher = pattern.matcher(line);
1884             if (matcher.matches()) {
1885                 int level = level(line);
1886                 String name = matcher.group(1);
1887                 int begin = matcher.end(1) + 1;
1888 
1889                 result = DocumentHelper.createElement(FIELD_LIST).addAttribute(
1890                         LEVEL, String.valueOf(level)).addAttribute(NAME,
1891                         name);
1892 
1893                 if (!in.eof()) {
1894                     String[] content = readBlock(level + 1);
1895                     line += " " + joinBlock(content);
1896                 }
1897                 String text = line.substring(begin).trim();
1898 
1899                 result.addText(text);
1900             }
1901         }
1902 
1903         endPeek();
1904         return result;
1905     }
1906 
1907     /**
1908      * read definition list
1909      * 
1910      * <pre>
1911      * un autre mot
1912      *   une autre definition
1913      * le mot : la classe
1914      *   la definition
1915      * le mot : la classe 1 : la classe 2
1916      *   la definition
1917      * </pre>
1918      * 
1919      * @return &lt;definition_list level="[int]" term="[text]"
1920      *         classifiers="[text]"&gt;[text]&lt;/definition_list&gt;
1921      * @throws IOException
1922      */
1923     public Element peekDefinitionList() throws IOException {
1924         beginPeek();
1925 
1926         Element result = null;
1927         // in.skipBlankLines();
1928         String[] lines = in.readLines(2);
1929         if (lines.length == 2) {
1930             int level = level(lines[0]);
1931             int levelDef = level(lines[1]);
1932             if ((levelDef != lines[1].length()) && (level < levelDef)) {
1933                 in.unread(lines[1], true);
1934                 Pattern pattern = Pattern.compile("^\\s*([^:]+)(?: : (.*))?");
1935                 Matcher matcher = pattern.matcher(lines[0]);
1936                 if (matcher.matches()) {
1937                     String term = matcher.group(1);
1938                     String classifiers = matcher.group(2);
1939 
1940                     result = DocumentHelper.createElement(DEFINITION_LIST)
1941                             .addAttribute(LEVEL, String.valueOf(level))
1942                             .addAttribute(TERM, term).addAttribute(
1943                                     CLASSIFIERS, classifiers);
1944 
1945                     // poussin 20070207 don't read block here because can't
1946                     // interpret it correctly in JRSTReader
1947                     // if (!in.eof()) {
1948                     // String [] content = readBlock(level + 1);
1949                     // String text = joinBlock(content);
1950                     // result.addText(text);
1951                     // }
1952                 }
1953             }
1954         }
1955 
1956         endPeek();
1957         return result;
1958     }
1959 
1960     /**
1961      * read enumarted list
1962      * 
1963      * can be: <li>1, 2, 3, ... <li>a, b, c, ... <li>A, B, C, ... <li>i, ii,
1964      * iii, iv, ... <li>I, II, III, IV, ...
1965      * 
1966      * or # for auto-numbered
1967      * 
1968      * <pre>
1969      * 
1970      * 1. next line 1) next line (1) first line
1971      * 
1972      * </pre>
1973      * 
1974      * @return &lt;enumerated_list level="[int]" start="[number]"
1975      *         prefix="[char]" suffix="[char]"
1976      *         enumtype="[(arabic|loweralpha|upperalpha|lowerroman|upperroman]"&gt
1977      *         ;[text]&lt;/enumerated_list&gt;
1978      * @throws IOException
1979      */
1980     public Element peekEnumeratedList() throws IOException {
1981         beginPeek();
1982 
1983         Element result = null;
1984         // in.skipBlankLines();
1985 
1986         String line = in.readLine();
1987         if (line != null) {
1988             Pattern pattern = Pattern
1989                     .compile("^\\s*(\\(?)(#|\\d+|[a-z]|[A-Z]|[ivxlcdm]+|[IVXLCDM]+)([\\.)]) [^\\s].*");
1990             Matcher matcher = pattern.matcher(line);
1991             if (matcher.matches()) {
1992                 int level = level(line);
1993                 String prefix = matcher.group(1);
1994                 String start = matcher.group(2);
1995                 String suffix = matcher.group(3);
1996                 int begin = matcher.end(3);
1997 
1998                 // arabic|loweralpha|upperalpha|lowerroman|upperroman
1999                 String enumtype = "auto";
2000                 if (start.matches("\\d+")) {
2001                     enumtype = "arabic";
2002                 } else if (start.matches("(i|[ivxlcdm][ivxlcdm]+)")) {
2003                     enumtype = "lowerroman";
2004                     start = "1"; // TODO transform romain to arabic
2005                 } else if (start.matches("(I|[IVXLCDM][IVXLCDM]+)")) {
2006                     enumtype = "upperroman";
2007                     start = "1"; // TODO transform romain to arabic
2008                 } else if (start.matches("[a-z]+")) {
2009                     enumtype = "loweralpha";
2010                     start = String.valueOf((int) start.charAt(0) - (int) 'a');
2011                 } else if (start.matches("[A-Z]+")) {
2012                     enumtype = "upperalpha";
2013                     start = String.valueOf((int) start.charAt(0) - (int) 'A');
2014                 }
2015 
2016                 result = DocumentHelper.createElement(ENUMERATED_LIST)
2017                         .addAttribute(LEVEL, String.valueOf(level))
2018                         .addAttribute(START, start).addAttribute(PREFIX,
2019                                 prefix).addAttribute(SUFFIX, suffix)
2020                         .addAttribute(ENUMTYPE, enumtype);
2021 
2022                 if (line.endsWith(": ::")) {
2023                     line = line.substring(0, line.length() - " ::".length());
2024                     in.unread("::", true);
2025                     
2026                 } else if (line.endsWith("::")) {
2027                     line = line.substring(0, line.length() - ":".length()); // keep
2028                     // one
2029                     // :
2030                     in.unread("::", true);
2031                 }   
2032                 
2033                 
2034                 if (!in.eof()) {
2035                     String[] content = readBlock(level + 1);
2036                     String tempLine = " " + joinBlock(content);
2037                                
2038                     if (tempLine.endsWith(": ::")) {
2039                         tempLine = tempLine.substring(0, tempLine.length() - " ::".length());
2040                         in.unread("::", true);
2041                         
2042                     } else if (tempLine.endsWith("::")) {
2043                         tempLine = tempLine.substring(0, tempLine.length() - ":".length()); // keep
2044                         // one
2045                         // :
2046                         in.unread("::", true);
2047                     }   
2048                     
2049                     line+=tempLine;
2050                     
2051                     
2052                 }
2053                 String text = line.substring(begin).trim();
2054 
2055                 result.addText(text);
2056                 in.skipBlankLines();
2057             }
2058         }
2059 
2060         endPeek();
2061         return result;
2062     }
2063 
2064     /**
2065      * Parse un titre simple ou double
2066      * 
2067      * simple:
2068      * 
2069      * <pre>
2070      * 
2071      * Le titre ========
2072      * 
2073      * </pre>
2074      * 
2075      * double:
2076      * 
2077      * <pre>
2078      * 
2079      * ============ le titre ============
2080      * 
2081      * </pre>
2082      * 
2083      * @return &lt;title level="[int]" type="[simple|double]" char="[underline
2084      *         char]"&gt;
2085      * @throws IOException
2086      */
2087     public Element peekTitle() throws IOException {
2088         beginPeek();
2089 
2090         Element result = null;
2091         // in.skipBlankLines();
2092         String line = in.readLine();
2093 
2094         if (line != null) {
2095             if (startsWithTitleChar(line)) {
2096                 String[] titles = in.readLines(2);
2097                 if (titles.length == 2 && line.length() >= titles[0].length()
2098                         && line.length() == titles[1].length()
2099                         && line.equals(titles[1])) {
2100                     result = DocumentHelper.createElement(TITLE).addAttribute(
2101                             TYPE, "double").addAttribute(CHAR,
2102                             titles[1].substring(0, 1)).addText(titles[0]);
2103                 }
2104             } else {
2105                 String title = in.readLine();
2106                 if (title != null
2107                         && startsWithTitleChar(title)
2108                         && line.replaceFirst("\\s*$", "").length() == title
2109                                 .length()) {
2110 
2111                     result = DocumentHelper.createElement(TITLE).addAttribute(
2112                             TYPE, "simple").addAttribute(CHAR,
2113                             title.substring(0, 1)).addText(
2114                             line.replaceFirst("\\s*$", ""));
2115                 }
2116             }
2117         }
2118 
2119         if (result != null) {
2120             // add level information
2121             String titleLevel = result.attributeValue(CHAR);
2122 
2123             if ("double".equals(result.attributeValue(TYPE))) {
2124                 titleLevel += titleLevel;
2125             }
2126             int level = titleLevels.indexOf(titleLevel);
2127             if (level == -1) {
2128                 level = titleLevels.size();
2129                 titleLevels.add(titleLevel);
2130             }
2131             result.addAttribute(LEVEL, String
2132                     .valueOf(JRSTReader.MAX_SECTION_DEPTH + level));
2133         }
2134 
2135         endPeek();
2136         return result;
2137     }
2138         
2139     public Element peekTarget() throws IOException {
2140         beginPeek();
2141 
2142         String line = in.readLine();
2143         Element result = null;
2144         if (line != null) {
2145             if (line.matches("^\\s*\\.\\.\\s_[^_:].+:.*")) {
2146                 result = DocumentHelper.createElement(TARGET);
2147                 Matcher matcher = Pattern.compile("\\.\\.\\s_").matcher(line);
2148                 if (matcher.find()) {
2149                     int i = line.indexOf(':');
2150                     result.addAttribute(ID, URLEncoder.encode(line.substring(matcher.end(), i)
2151                             .toLowerCase().replaceAll(" ", "-"), "UTF-8"));
2152                     result.addAttribute(LEVEL, "" + level(line));
2153                 }
2154             }
2155         }
2156         endPeek();
2157         return result;
2158     }
2159 
2160     /**
2161      * .. _frungible doodads: http://www.example.org/
2162      * 
2163      * @return Element
2164      * @throws IOException
2165      */
2166     public LinkedList<Element> refTarget() throws IOException {
2167         beginPeek();
2168 
2169         String[] lines = in.readAll();
2170         LinkedList<Element> result = new LinkedList<Element>();
2171         for (String line : lines) {
2172             if (line.matches("^\\s*\\.\\.\\s_[^_:].+:.*")) {
2173                 result.add(DocumentHelper.createElement(TARGET));
2174                 Matcher matcher = Pattern.compile("\\.\\.\\s_").matcher(line);
2175                 if (matcher.find()) {
2176                     boolean done = false;
2177                     for (int i = matcher.end(); i < line.length() && !done; i++) {
2178                         if (line.charAt(i) == ':') {
2179                             result.getLast().addAttribute(LEVEL,
2180                                     "" + level(line));
2181                             result.getLast().addAttribute(
2182                                     ID,
2183                                     URLEncoder.encode(line.substring(matcher.end(), i)
2184                                     .replaceAll(" ", "-").toLowerCase(), "UTF-8"));
2185                             result.getLast().addAttribute(
2186                                     NAME,
2187                                     line.substring(matcher.end(), i)
2188                                             .toLowerCase());
2189                             if (i + 2 > line.length()) {
2190                                 line = in.readLine();
2191                                 // FIXME 20071129 chatellier
2192                                 // line = null if link is non well formed
2193                                 // .. _Unifying types and classes in Python:
2194                                 // miss uri
2195                                 if (line == null) {
2196                                     line = "";
2197                                 }
2198                                 result.getLast().addAttribute(REFURI,
2199                                         line.trim());
2200                             } else {
2201                                 result.getLast().addAttribute(REFURI, line.substring(i + 2, line.length()));
2202                             }
2203 
2204                             done = true;
2205                         }
2206                     }
2207                 }
2208             }
2209         }
2210         endPeek();
2211         return result;
2212     }
2213 
2214     /**
2215      * .. __: http://www.python.org
2216      * 
2217      * @return Element
2218      * @throws IOException
2219      */
2220     private Element peekTargetAnonymousBody() throws IOException {
2221         beginPeek();
2222         Element result = null;
2223         String line = in.readLine();
2224         if (line != null) {
2225             if (line.matches("^\\s*__ .+$|^\\s*\\.\\. __\\:.+$")) {
2226                 result = DocumentHelper.createElement(TARGETANONYMOUS);
2227                 result.addAttribute(LEVEL, "" + level(line));
2228 
2229             }
2230         }
2231 
2232         endPeek();
2233         return result;
2234     }
2235 
2236     /**
2237      * .. comment
2238      * 
2239      * @return Element
2240      * @throws IOException
2241      */
2242     private Element peekComment() throws IOException {
2243         beginPeek();
2244         Element result = null;
2245         String line = in.readLine();
2246         if (line != null) {
2247             if (line.matches("^\\.\\.\\s+.*$")) {
2248                 result = DocumentHelper.createElement(COMMENT);
2249                 result.addAttribute(LEVEL, "0");
2250                 result.addAttribute(XMLSPACE, "preserve");
2251 
2252                 // first line is part of comment
2253                 result.setText(line.substring(2).trim());
2254                 line = in.readLine();
2255                 if(line != null) {
2256                     int level = level(line);
2257                     if (level > 0) {
2258                         String[] lines = readBlock(level);
2259                         String text = line.substring(level);
2260                         for (String l : lines) {
2261                             text += "\n" + l.substring(level);
2262                         }
2263                         result.addText(text);
2264                     }
2265                 }
2266             }
2267         }
2268 
2269         endPeek();
2270         return result;
2271     }
2272 
2273     /**
2274      * .. comment
2275      *
2276      * @return Element
2277      * @throws IOException
2278      */
2279     public List<Element> peekAllComment() throws IOException {
2280         beginPeek();
2281         List<Element> result = new ArrayList<Element>();
2282         String[] lines = in.readWhile("^\\.\\.\\s*.*$");
2283         if (lines != null) {
2284 //            int levelRef = level(line);
2285             for (String line : lines) {
2286                 Element comment = DocumentHelper.createElement(COMMENT);
2287                 comment.addAttribute(LEVEL, "0");
2288                 comment.addAttribute(XMLSPACE, "preserve");
2289 
2290                 // first line is part of comment
2291                 comment.setText(line.substring(2).trim());
2292                 result.add(comment);
2293 
2294 //                int level = level(line);
2295 //                if (level == levelRef) {
2296 //                    String[] lines = readBlock(level);
2297 //                    String text = line.substring(level);
2298 //                    for (String l : lines) {
2299 //                        text += "\n" + l.substring(level);
2300 //                    }
2301 //                    result.addText(text);
2302 //                }
2303             }
2304             in.mark();
2305         }
2306 
2307         endPeek();
2308         return result;
2309     }
2310 
2311     /**
2312      * .. _frungible doodads: http://www.example.org/
2313      * 
2314      * @return Element
2315      * @throws IOException
2316      */
2317     public Element peekFootnote() throws IOException {
2318         beginPeek();
2319         Element result = null;
2320         String line = in.readLine();
2321         if (line != null) {
2322             if (line.matches("^\\s*\\.\\.\\s\\[(#|[0-9]|\\*).*\\]\\s.+$")) {
2323                 result = DocumentHelper.createElement(FOOTNOTES);
2324                 boolean bLine = false;
2325                 do {
2326 
2327                     bLine = false;
2328                     Element footnote = result.addElement(FOOTNOTE);
2329                     Matcher matcher = Pattern.compile("\\.\\.\\s\\[").matcher(
2330                             line);
2331 
2332                     if (matcher.find()) {
2333 
2334                         boolean done = false;
2335                         for (int i = matcher.end(); i < line.length() && !done; i++) {
2336                             if (line.charAt(i) == ']') {
2337 
2338                                 result.addAttribute(LEVEL, "" + level(line));
2339                                 String id = line.substring(matcher.end(), i);
2340                                 if (id.matches("\\*")) {
2341                                     footnote.addAttribute(TYPE, AUTOSYMBOL);
2342                                 } else if (id.matches("[0-9]")) {
2343                                     footnote.addAttribute(TYPE, NUM);
2344                                     footnote.addAttribute(NAME, id);
2345                                 } else if (id.equals("#")) {
2346                                     footnote.addAttribute(TYPE, AUTONUM);
2347                                 } else {
2348                                     footnote.addAttribute(TYPE,
2349                                             AUTONUMLABEL);
2350                                     footnote.addAttribute(NAME, id
2351                                             .substring(1));
2352                                 }
2353                                 String text = line.substring(i + 2, line
2354                                         .length());
2355 
2356                                 int levelAv = level(line);
2357                                 line = in.readLine();
2358                                 if (line != null) {
2359                                     if (line
2360                                             .matches("^\\s*\\.\\.\\s\\[(#|[0-9]|\\*).*\\]\\s.+$")) {
2361                                         bLine = true;
2362                                     } else {
2363 
2364                                         int level = level(line);
2365                                         if (levelAv < level) {
2366                                             String[] lines = in
2367                                                     .readWhile("(^ {" + level
2368                                                             + "}.*)|(\\s*)");
2369                                             text += "\n" + line.trim();
2370                                             for (String l : lines) {
2371                                                 text += "\n" + l.trim();
2372                                             }
2373 
2374                                         } else if (line.matches("\\s*")) {
2375                                             level = levelAv + 1;
2376                                             String[] lines = in
2377                                                     .readWhile("(^ {" + level
2378                                                             + "}.*)|(\\s*)");
2379                                             text += "\n" + line.trim();
2380                                             for (String l : lines) {
2381                                                 text += "\n" + l.trim();
2382                                             }
2383 
2384                                         }
2385                                     }
2386                                     if (!bLine) {
2387                                         in.skipBlankLines();
2388                                         String[] linesTmp = in
2389                                                 .readWhile("^\\s*\\.\\.\\s\\[(#|[0-9]|\\*).*\\]\\s.+$");
2390 
2391                                         if (linesTmp.length > 0) {
2392                                             line = linesTmp[0];
2393                                             bLine = true;
2394                                         }
2395                                     }
2396 
2397                                 }
2398                                 if (line == null) {
2399                                     line = "";
2400                                 }
2401                                 footnote.setText(text);
2402                                 done = true;
2403                             }
2404 
2405                         }
2406                     }
2407 
2408                 } while (bLine);
2409             }
2410         }
2411         endPeek();
2412         return result;
2413     }
2414 
2415     /**
2416      * Read block text, block text have same indentation
2417      * 
2418      * @param minLeftMargin
2419      *            min left blank needed to accept to read block
2420      * @return String
2421      * @throws IOException
2422      */
2423     private String readBlockWithBlankLine(int level) throws IOException {
2424         String txt = "";
2425         String[] lines = in.readWhile("(^ {" + level + "}.*)|(\\s*)");
2426         while (lines.length > 0) {
2427             for (String l : lines) {
2428                 l = l.trim();
2429                 txt += l + "\n";
2430 
2431             }
2432             lines = in.readWhile("(^ {" + level + "}.*)|(\\s*)");
2433         }
2434         return txt;
2435     }
2436 
2437     /**
2438      * Lit les premieres ligne non vide et les retourne, rien n'est modifier par
2439      * rapport aux positions dans le fichier. Util pour afficher a l'utilisateur
2440      * les lignes qui ont produit une erreur
2441      * 
2442      * @return les lignes non vides
2443      * @throws IOException
2444      */
2445     public String readNotBlanckLine() throws IOException {
2446         beginPeek();
2447         in.skipBlankLines();
2448         String line = joinBlock(in.readUntilBlank(), "\n", false);
2449         endPeek();
2450         return line;
2451     }
2452 
2453     /**
2454      * return the number of line read
2455      * 
2456      * @return int
2457      */
2458     public int getLineNumber() {
2459         return in.getLineNumber();
2460     }
2461 
2462     /**
2463      * return the number of char read
2464      * 
2465      * @return int
2466      */
2467     public int getCharNumber() {
2468         return in.getCharNumber();
2469     }
2470 
2471     /**
2472      * return true if line can be underline or overline for title
2473      * 
2474      * @param line
2475      * @return boolean
2476      */
2477     private boolean startsWithTitleChar(String line) {
2478         if (line == null || line.length() < 2) {
2479             return false;
2480         }
2481         // est-ce que la ligne est constituer entierement du meme caractere et
2482         // qu'il y en a au moins 2
2483         boolean result = line
2484                 .matches("([" + escapeRegex(TITLE_CHAR) + "])\\1+");
2485         return result;
2486     }
2487 
2488     /**
2489      * @param title_charescapeRegex
2490      * @return String
2491      */
2492     private String escapeRegex(String text) {
2493         String result = text.replaceAll("([()[\\\\]*+?.])", "\\\\$1");
2494         return result;
2495     }
2496 
2497     /**
2498      * @param String
2499      *            line
2500      * @return int
2501      * @throws IOException
2502      */
2503     private int level(String line) {
2504         int result = 0;
2505         String sTmp = line.replaceAll("\\s", " ");
2506         while (sTmp.length() > result && sTmp.charAt(result) == ' ') {
2507             result++;
2508         }
2509         return result;
2510     }
2511 }