Sgml2Xml.java
001 /*
002  *  Sgml2Xml.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Cristian URSU,  4/July/2000
013  *
014  *  $Id: Sgml2Xml.java 17600 2014-03-08 18:47:11Z markagreenwood $
015  */
016 
017 package gate.sgml;
018 
019 import java.io.File;
020 import java.io.IOException;
021 import java.net.MalformedURLException;
022 import java.util.*;
023 
024 import gate.Document;
025 import gate.util.Files;
026 
027 
028 /**
029   * Not so fast...
030   * This class is not a realy Sgml2Xml convertor.
031   * It takes an SGML document and tries to prepare it for an XML parser
032   * For a true conversion we need an Java SGML parser...
033   * If you know one let me know....
034   *
035   * What does it do:
036   <ul>
037   *  <li>If it finds something like this : &lt;element attribute = value&gt;
038   *      it will produce: &lt;element attribute = "value"&gt;
039   *  <li>If it finds something like this : &lt;element something
040   *      attribute2=value&gt;it will produce : &lt;element
041   *      defaultAttribute="something" attribute2="value"&gt;
042   *  <li>If it finds : &lt;element att1='value1 value2' att2="value2
043   *      value3"&gt; it will produce: &lt;element att1="value1 value2"
044   *      att2="value2 value3"&gt;
045   *  <li>If it finds : &lt;element1&gt; &lt;elem&gt;text &lt;/element1&gt;
046   *      will produce: &lt;element1&gt; &lt;elem&gt;text&lt;elem&gt;
047   *      &lt;/element1&gt;
048   *  <li>If it find : &lt;element1&gt; &lt;elem&gt;[white spaces]
049   *      &lt;/element1&gt;,
050   *      it will produce:&lt;element1&gt; &lt;elem/&gt;[white spaces]&lt;
051   *      /element1&gt;
052   </ul>
053   * What doesn't:
054   <ul>
055   *  <li>Doesn't expand the entities. So the entities from the SGML document
056   *      must be resolved by the XML parser
057   *  <li>Doesn't replace internal entities with their corresponding value
058   </ul>
059   */
060 
061 public class Sgml2Xml{
062 
063   /**
064     * The constructor initialises some member fields
065     @param SgmlDoc the content of the Sgml document that will be modified
066     */
067   public Sgml2Xml(String SgmlDoc){
068     // create a new modifier
069     m_modifier = new StringBuffer(SgmlDoc);
070     // create a new dobiousElements list
071     // se the explanatin at the end of the class
072     dubiousElements = new ArrayList<CustomObject>();
073     stack = new Stack<CustomObject>();
074   }
075 
076   /**
077     * The other constructor
078     @param doc The Gate document that will be transformed to XML
079     */
080   public Sgml2Xml(Document doc){
081     // set as a member
082     m_doc = doc;
083 
084     // create a new modifier
085     m_modifier = new StringBuffer(m_doc.getContent().toString());
086 
087     // create a new dobiousElements list
088     // se the explanatin at the end of the class
089     dubiousElements = new ArrayList<CustomObject>();
090     stack = new Stack<CustomObject>();
091 
092   }
093 
094 /*  I keep this just in case I need some more debuging
095 
096   public static void main(String[] args){
097     Sgml2Xml convertor =
098       new Sgml2Xml("<w VVI='res trtetre\" relu = \"stop\">say
099       <w VBZ>is\n<trunc> <w UNC>th </trunc>");
100     try{
101       Out.println(convertor.convert());
102     } catch (Exception e){
103       e.printStackTrace(Err.getPrintWriter());
104     }
105   }
106   */
107 
108   /**
109     * It analises the char that was red in state 1
110     * If it finds '<' it then goes to state 2
111     * Otherwise it stays in state 1 and keeps track about the text that is not
112     * white spaces.
113     */
114   private void doState1(char currChar){
115     if ('<' == currChar){
116       // change to state 2
117       m_currState = 2;
118       if (!stack.isEmpty()){
119         // peek the element from the top of the stack
120         CustomObject o = stack.peek();
121         // set some properties for this element
122         // first test to find out if text folows this element charPos > 0
123         if (charPos > 0){
124           // this is not an empty element because there is text that follows
125           // set the element from the top of the stack to be a non empty one
126           o.setClosePos(charPos);
127           o.setEmpty(false);
128           // reset the charPos
129           charPos = 0;
130         }//if (charPos > 0)
131       }//if (!stack.isEmpty())
132     }//if ('<' == m_currChar)
133     // if currChar is not whiteSpace then save the position of the last
134     // char that was read
135     if (('<' != currChar&& !isWhiteSpace(currChar))
136       charPos = m_cursor;
137   }//doState1
138 
139   /**
140     We came from state 1 and just read '<'
141     If currChar == '/' -> state 11
142     If is a char != white spaces -> state 3
143     stay in state 2 while there are only white spaces
144   */
145   private void doState2(char currChar){
146     if ('/' == currChar){
147       // go to state 11
148       m_currState = 11;
149     }
150     // if currChar is a char != white spaces  then go to state 3
151     if (('/' != m_currChar&& !isWhiteSpace(m_currChar)){
152       // save the position where starts the element's name
153       // we need that in order to be able to read the current tag name
154       // this name it will be read from m_modifier using the substring() method
155       elemNameStart = m_cursor -1;
156       // go to state 3
157       m_currState = 3;
158     }
159   }// doState2
160 
161   /**
162     * Just read the first char from the element's name and now analize the next
163     * char.
164     * If '>' the elem name was a single char -> state 1
165     * IF is WhiteSpaces -> state 4
166     * Otherwise stay in state 3 and read the elemnt's name
167     */
168   private void doState3(char currChar){
169     if '>' == currChar ){
170 
171       // save the pos where the element's name ends
172       elemNameEnd = m_cursor - 1;
173 
174       // this is also the pos where to insert '/' for empty elements.
175       // In this case we have this situation <w> sau < w>
176       closePos = m_cursor - 1;
177 
178       // get the name of the element
179       elemName = m_modifier.substring(elemNameStart,elemNameEnd);
180 
181       // we put the element into stack
182       // we think in this point that the element is empty...
183       performFinalAction(elemName, closePos);
184 
185       // go to state 1
186       m_currState = 1;
187     }
188     if (isWhiteSpace(currChar)){
189       // go to state 4
190       m_currState = 4;
191 
192       // save the pos where the element's name ends
193       elemNameEnd = m_cursor - 1;
194 
195       // get the name of the element
196       elemName = m_modifier.substring(elemNameStart,elemNameEnd);
197     }
198   }// doState3
199 
200   /**
201     * We read the name of the element and we prepare for '>' or attributes
202     * '>' -> state 1
203     * any char !- white space -> state 5
204     */
205   private void doState4(char currChar){
206     if '>' == currChar ){
207       // this is also the pos where to insert '/' for empty elements in this case
208       closePos = m_cursor -;
209 
210       // we put the element into stack
211       // we think in this point that the element is empty...
212       performFinalAction(elemName, closePos);
213 
214       // go to state 1
215       m_currState = 1;
216     }
217     if (( '>' != currChar && !isWhiteSpace(currChar)){
218       // we just read the first char from the attrib name or attrib value..
219       // go to state 5
220       m_currState = 5;
221 
222       // remember the position where starts the attrib or the value of an attrib
223       attrStart = m_cursor - 1;
224     }
225   // doState4
226 
227   /**
228     * '=' -> state 6
229     * '>' -> state 4 (we didn't read an attribute but a value of the
230     * defaultAtt )
231     * WS (white spaces) we don't know yet if we read an attribute or the value
232     * of the defaultAttr -> state 10
233     * This state modifies the content onf m_modifier ... it adds text
234     */
235   private void doState5(char currChar){
236     if '=' == currChar )
237           m_currState = 6;
238     if '>' == currChar ){
239       // this mean that the attribute was a value and we have to create
240       // a default attribute
241       // the same as in state 10
242       attrEnd = m_cursor - ;
243       m_modifier.insert(attrEnd,'"');
244       m_modifier.insert(attrStart,"defaultAttr=\"");
245 
246       // go to state 4
247       m_currState = 4;
248 
249       // parse again the entire sequence from state 4 before reading any char
250       m_cursor = attrStart;
251     }
252     if (isWhiteSpace(currChar)){
253       // go to state 10
254       m_currState = 10;
255 
256       // record the position where ends this attribute
257       attrEnd = m_cursor - 1;
258     }
259   // doState5
260 
261   /**
262     * IF we read ' or " then we have to get prepared to read everything until
263     * the next ' or "
264     * If we read a char then -> state 8;
265     * Stay here while we read WS
266     */
267   private void doState6(char currChar){
268     if ( ('\'' == currChar|| ('"' == currChar) ){
269       endPair = currChar;
270       if ('\'' == currChar){
271 
272         // we have to replace ' with "
273         m_modifier = m_modifier.replace(m_cursor - 1, m_cursor,"\"");
274       }
275       m_currState = 7;
276     }
277     if ( ('\'' != currChar&& ('"' != currChar&& !isWhiteSpace(currChar)){
278 
279       // this means that curChar is any char
280       m_currState = 8;
281 
282       // every value must be inside this pair""
283       m_modifier.insert(m_cursor - 1'"');
284 
285       // insert implies the modification of m_cursor
286       // we increment m_cursor in order to say in the same position and to
287       // anulate the efect of insert.
288       m_cursor ++;
289     }
290   }// doState6
291 
292   /**
293     * If we find the pair ' or " go to state 9
294     * Otherwhise read everything and stay in state 7
295     * If in state 7 we read '>' then we add automaticaly a " at the end and go
296     * to state 1
297     */
298   private void doState7(char currChar){
299     //if ( ('\'' == currChar) || ('"' == currChar) ){
300     if endPair == currChar ){
301       if ('\'' == currChar){
302 
303         // we have to replace ' with "
304         m_modifier = m_modifier.replace(m_cursor - 1, m_cursor,"\"");
305       }
306       // reset the endPair
307       endPair = ' ';
308       m_currState = 9;
309     }
310 
311     if ('>' == currChar){
312       // go to state 1
313       m_currState = 1;
314 
315       // insert the final " ata the end
316       m_modifier.insert(m_cursor - 1'"');
317 
318       // go to te current possition (because of insert)
319       m_cursor ++;
320 
321       performFinalAction(elemName, m_cursor - 1);
322     }
323 
324   }// doState7
325 
326   /**
327     * If '>' go to state 1
328     * If WS go to state 9
329     * Stays in state 8 and read the attribute's value
330     */
331   private void doState8(char currChar){
332 
333     if ('>' == currChar){
334       // go to state 1
335       m_currState = 1;
336 
337       // complete the end " ( <elem attr="value> )
338       m_modifier.insert(m_cursor - 1'"');
339 
340       // go to te current possition (because of insert)
341       m_cursor ++;
342 
343       // we finished to read a beggining tag
344       // see the method definition for more details
345       performFinalAction(elemName, m_cursor - 1);
346     }
347     if (isWhiteSpace(currChar)){
348       // go to state 9
349       m_currState = 9;
350 
351       // add the ending " char
352       m_modifier.insert(m_cursor - 1'"');
353 
354       // increment the cursor in order to anulate the effect of insert
355       m_cursor ++;
356     }
357   // doState8
358   /**
359     * Here we prepare to read another attrib, value pair (any char -> state 5)
360     * If '>' we just read a beggining tag -> state 1
361     * Stay here while read WS
362     */
363   private void doState9(char currChar){
364     if ('>' == currChar){
365       // go to state 1
366       m_currState = 1;
367 
368       // add the object to the stack
369       performFinalAction(elemName, m_cursor - 1);
370     }
371     if (('>' != currChar&& !isWhiteSpace(m_currChar)){
372       // this is the same as state 4->5
373       m_currState = 5;
374       attrStart = m_cursor - 1;
375     }
376   }//doState9
377 
378   /**
379     * If any C -> state 4
380     * If '=' state 6
381     * Stays here while reads WS
382     */
383   private void doState10(char currChar){
384     if ('=' == currChar)
385              m_currState = 6;
386     if ( ('=' != currChar&& !isWhiteSpace(currChar)){
387       // this mean that the attribute was a value and we have to create
388       // a default attribute
389       m_modifier.insert(attrEnd,'"');
390       m_modifier.insert(attrStart,"defaultAttr=\"");
391 
392       // go to state 4
393       m_currState = 4;
394 
395       m_cursor = attrStart;
396     }
397   }// doState10
398 
399   /**
400     * We are preparing to read the and definition of an element
401     * Stays in this state while reading WS
402     */
403   private void doState11(char currChar){
404     if (!isWhiteSpace(currChar)){
405       m_currState = 12;
406       elemNameStart = m_cursor - 1;
407     }
408   // doState11
409 
410   /**
411     * Here we read the element's name ...this is an end tag
412     * Stays here while reads a char
413     */
414   private void doState12(char currChar) {
415     if ('>' == currChar){
416       elemNameEnd = m_cursor - 1;
417       elemName = m_modifier.substring(elemNameStart,elemNameEnd);
418       performActionWithEndElem(elemName);
419       m_currState = 1;
420     }
421     if (isWhiteSpace(currChar)){
422       m_currState = 13;
423       elemNameEnd = m_cursor - 1;
424     }
425   }//doState12
426 
427   /**
428     * If '>' -> state 1
429     * Stays here while reads WS
430     */
431   private void doState13(char currChar) {
432     if ('>' == currChar){
433       elemName = m_modifier.substring(elemNameStart,elemNameEnd);
434       performActionWithEndElem(elemName);
435       m_currState = 1;
436     }
437   // doState13
438 
439   /**
440     This method is responsable with document conversion
441   */
442   public String convert()throws IOException,MalformedURLException {
443     while (thereAreCharsToBeProcessed()) {
444       // read() gets the next char and increment the m_cursor
445       m_currChar = read();
446       switch(m_currState){
447         case 1:   doState1(m_currChar);break;
448         case 2:   doState2(m_currChar);break;
449         case 3:   doState3(m_currChar);break;
450         case 4:   doState4(m_currChar);break;
451         case 5:   doState5(m_currChar);break;
452         case 6:   doState6(m_currChar);break;
453         case 7:   doState7(m_currChar);break;
454         case 8:   doState8(m_currChar);break;
455         case 9:   doState9(m_currChar);break;
456         case 10:  doState10(m_currChar);break;
457         case 11:  doState11(m_currChar);break;
458         case 12:  doState12(m_currChar);break;
459         case 13:  doState13(m_currChar);break;
460       }// switch(m_currState)
461     }// while (thereAreCharsToBeProcessed())
462 
463     // put all the elements from the stack into the dubiousElements list
464     // we do that in order to colect all the dubious elements
465     while (!stack.isEmpty()) {
466       CustomObject obj = stack.pop();
467       dubiousElements.add(obj);
468     }
469 
470     // sort the dubiousElements list descending on closePos...
471     // This is vital for the alghorithm because we have to make
472     // all the modifications from the bottom to the top...
473     // If we fail to do that, insert will change indices and
474     // CustomObject.getClosePos() will not be acurate anymore.
475     Collections.sort(dubiousElements, new MyComparator());
476 
477     //here we resolve all the dubious Elements...
478     // see the description of makeFinalModifications() method
479     ListIterator<CustomObject> listIterator = dubiousElements.listIterator();
480     while (listIterator.hasNext()){
481       CustomObject obj = listIterator.next();
482       makeFinalModifications(obj);
483     }
484 
485     //finally add the XML prolog
486     m_modifier.insert(0,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
487     //Out.println(m_modifier.toString());
488 /*
489     // get a InputStream from m_modifier and write it into a temp file
490     // finally return the URI of the new XML document
491     ByteArrayInputStream is = new ByteArrayInputStream(
492                                               m_modifier.toString().getBytes()
493                                                        );
494 */
495     // this method is in gate.util package
496     File file = Files.writeTempFile(m_modifier.toString(),"UTF-8");
497 
498     //return m_doc.getSourceURL().toString();
499     return file.toURI().toURL().toString();
500   }// convert()
501 
502   /**
503     * This method tests to see if there are more char to be read
504     * It will return false when there are no more chars to be read
505     */
506   private boolean thereAreCharsToBeProcessed() {
507     if (m_cursor < m_modifier.length()) return true;
508     else return false;
509   }//thereAreCharsToBeProcessed
510 
511   /**
512     * This method reads a char and increments the m_cursor
513     */
514   private char read(){
515     return m_modifier.charAt(m_cursor ++);
516   }//read
517 
518   /**
519     * This is the action when we finished to read the entire tag
520     * The action means that we put the tag into stack and consider that is empty
521     * as default
522     */
523   private void performFinalAction(String elemName, int pos) {
524     // create anew CustomObject
525     CustomObject obj = new CustomObject();
526 
527     // set its properties
528     obj.setElemName(elemName);
529     obj.setClosePos(pos);
530 
531     // default we consider every element to be empty
532     // in state 1 we modify that if the element is followed by text
533     obj.setEmpty(true);
534     stack.push(obj);
535   // performFinalAction
536 
537   /**
538     * This is the action performed when an end tag is read.
539     * The action consists in colecting all the dubiosElements(elements without
540     * an end tag). They are considered dubious because we don't know if they
541     * are empty or may be closed... Only the DTD can provide this information.
542     * We don't have a DTD so we will consider that all dubious elements
543     * followed by text will close at the end of the text...
544     * If a dubious element is followed by another element then is
545     * automaticaly considered an empty element.
546     *
547     @param elemName is the the name of the end tag that was read
548     */
549   private void performActionWithEndElem(String elemName) {
550     CustomObject obj    = null;
551     boolean      stop = false;
552 
553     // get all the elements that are dubious from the stack
554     // the iteration will stop when an element is equal with elemName
555     while (!stack.isEmpty() && !stop){
556 
557       // eliminate the object from the stack
558       obj = stack.pop();
559 
560       //if its elemName is equal with the param elemName we stop the itteration
561       if (obj.getElemName().equalsIgnoreCase(elemName)) stop = true;
562 
563       // otherwhise add the element to the doubiousElements list
564       else dubiousElements.add(obj);
565     }
566   }//performActionWithEndElem
567 
568   /**
569     * This method is called after we read the entire SGML document
570     * It resolves the dobious Elements this way:
571     <ul>
572     <li>
573     * 1. We don't have a DTD so we will consider that all dubious elements
574     *    followed by text will close at the end of the text...
575     <li>
576     * 2. If a dubious element is followed by another element then is
577         automaticaly considered an empty element.
578     *
579     * An element is considered dubious when we don't know if it is  empty
580     * or may be closed...
581     *
582     @param aCustomObject an object from the dubiousElements list
583     */
584   private void makeFinalModifications(CustomObject aCustomObject) {
585     String endElement = null;
586     // if the element is empty then we add / before > like this:
587     // <w> -> <w/>
588     if (aCustomObject.isEmpty())
589         m_modifier.insert(aCustomObject.getClosePos(),"/");
590     // otherwhise we create an end element
591     // <w> -> </w>
592     else{
593       // create the end element
594       endElement = "</" + aCustomObject.getElemName() ">";
595       // insert it where the closePos indicates
596       m_modifier.insert(aCustomObject.getClosePos(), endElement);
597     }
598   // makeFinalModifications
599 
600   /**
601     * Tests if c is a white space char
602     */
603   private boolean isWhiteSpace(char c) {
604     return Character.isWhitespace(c);
605   }
606 
607   // this is a gate Document... It's content will be transferred to
608   // m_modifier
609   private Document m_doc = null;
610 
611   // this is the modifier that will transform an SGML document into an
612   // XML document
613   private StringBuffer m_modifier = null;
614 
615   // we need the stack to be able to remember the order of the tags
616   private Stack<CustomObject> stack = null;
617 
618   // this is a list with all the tags that are not colsed...
619   // some of them are empty tags and some of them are not...
620   private List<CustomObject> dubiousElements = null;
621 
622   // this is tre current position inside the modifier
623   private int m_cursor = 0;
624 
625   // the current state of the SGML2XML automata
626   private int m_currState = 1;
627 
628   // the char that was read from the m_modifier @ position m_cursor
629   private char m_currChar = ' ';
630 
631   // the fields above are used by the convert method and its auxiliary functions
632   // like doState1...13()
633 
634   // indicates the last position of a text character (one which is not a white
635   // space)
636   // it is used in doState1() when we have to decide if an element is empty or
637   // not
638   // We decide that based on this field
639   // If the charPos > 0 then it means that the object from the top of stack
640   // is followed by text and we consider that is not empty
641   private int charPos = 0;
642 
643   // is the current tag name
644   private String elemName = null;
645 
646   // indicates where in the m_modifier begins the current tag elemName
647   private int elemNameStart = 0;
648 
649   // indicates where in the m_modifier ends the current tag elemName
650   // we need that in order to be able to read the current tag name
651   // this name it will be read from m_modifier using the substring() method
652   // it will be something like this :
653   // elemName = m_modifier.substring(elemNameStart,elemNameEnd)
654   // Eg: <w attr1=val1> -> <[elemNameStart]w[elemNameEnd] [attr1=val1>
655   private int elemNameEnd = 0;
656 
657   // this is the position there a start tag ends like this:
658   // Eg: <w attr1=val1>  -> <w attr1=val1 [closePos]>
659   private int closePos = 0;
660 
661   //this is the position where an attribute starts...
662   // we need it when we have to add the defaultAttr (see state 5)
663   private int attrStart = 0;
664 
665     //this is the position where an attribute ends...
666   // we need it when we have to add the defaultAttr (see state 5) or to add "
667   // Eg: <w attr1=val1> -> <w [attrStart]attr1[attrEnd]=val1>
668   private int attrEnd = 0;
669 
670   // endPair field is used in states 6 and 7....
671   // When we read something like this :
672   // attr=' val1 val2 val3' endPair remembers what is the pair for the beginning
673   // string
674   // Note that a combination like: attr = ' val1 val2 " will have an unexpected
675   // behaviour...
676   // We need this field when we have the following situation
677   // attr1 = " val1 val2 ' val3" . We need to know what is the end pair for ".
678   // In this case we can't allow ' to be the endPair
679   private char endPair = ' ';
680 
681 // class Sgml2Xml
682 
683 /**
684   * The objects belonging to this class are used inside the stack
685   */
686 class  CustomObject {
687 
688   // constructor
689   public CustomObject() {
690     elemName = null;
691     closePos = 0;
692     empty = false;
693   }
694 
695   // accessor
696   public String getElemName() {
697     return elemName;
698   }
699 
700   public int getClosePos() {
701     return closePos;
702   }
703 
704   public boolean isEmpty() {
705     return empty;
706   }
707 
708   // modifiers
709   void setElemName(String anElemName) {
710     elemName = anElemName;
711   }
712 
713   void setClosePos(int aPos){
714     closePos = aPos;
715   }
716 
717   void setEmpty(boolean anEmptyValue) {
718     empty = anEmptyValue;
719   }
720 
721   // data fields
722   private String elemName = null;
723 
724   private int closePos = 0;
725 
726   private boolean empty = false;
727 
728 // CustomObject
729 
730 class MyComparator implements Comparator<CustomObject> {
731 
732   public MyComparator() {
733   }
734 
735   @Override
736   public int compare(CustomObject co1, CustomObject co2) {
737 
738     int result = 0;
739     if (co1.getClosePos() <   co2.getClosePos())  result = -1;
740     if (co1.getClosePos() ==  co2.getClosePos())  result =  0;
741     if (co1.getClosePos() >   co2.getClosePos())  result =  1;
742 
743     return -result;
744   // compare
745 
746 }// class MyComparator