Subversion Repositories wimsdev

Rev

Rev 3662 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.     Sketch Elements: Chemistry molecular diagram drawing tool.
  3.    
  4.     (c) 2008 Dr. Alex M. Clark
  5.    
  6.     Released as GNUware, under the Gnu Public License (GPL)
  7.    
  8.     See www.gnu.org for details.
  9. */
  10.  
  11. package WIMSchem.ds;
  12.  
  13. import WIMSchem.*;
  14.  
  15. import java.io.*;
  16. import java.util.*;
  17.  
  18. /*
  19.     An incredibly lightweight implementation of DOM-style access to XML content. Only a subset of XML files are supported, that
  20.     being simple combinations of elements, attributes and text. Overly sophisticated input files may break the reader. Also, some
  21.     of the pedantic XML treatment of whitespace is simplified (which suits the rest of this application nicely). Malformed XML
  22.     should generate vaguely helpful explanations, by and large.
  23. */
  24.  
  25. public class TrivialDOM
  26. {
  27.     public static final int TYPE_NODE=1;
  28.     public static final int TYPE_TEXT=2;
  29.  
  30.     class Node
  31.     {
  32.         private Node parentNode=null;
  33.         private String nodeName;
  34.         private Hashtable<String,String> nodeAttr;
  35.         private ArrayList<Object> children;
  36.        
  37.         public Node(String NodeName)
  38.         {
  39.             nodeName=NodeName;
  40.             nodeAttr=new Hashtable<String,String>();
  41.             children=new ArrayList<Object>();
  42.         }
  43.        
  44.         public Node parent() {return parentNode;}
  45.         public void setParent(Node Parent) {parentNode=Parent;}
  46.        
  47.         public String nodeName() {return nodeName;}
  48.         public void setNodeName(String Name) {nodeName=Name;}
  49.         public String attribute(String Attr) {return nodeAttr.containsKey(Attr) ? nodeAttr.get(Attr) : null;}
  50.         public void setAttribute(String Attr,String Value) {nodeAttr.put(Attr,Value);}
  51.         public String[] getAttributeNames()
  52.         {
  53.             Set<String> attr=nodeAttr.keySet();
  54.             String[] names=new String[attr.size()];
  55.             return attr.toArray(names);
  56.         }
  57.        
  58.         public int numChildren() {return children.size();}
  59.         public int childType(int N)
  60.         {
  61.             Object child=children.get(N);
  62.             if (child instanceof Node) return TYPE_NODE;
  63.             if (child instanceof Text) return TYPE_TEXT;
  64.             return 0;
  65.         }
  66.         public Node getChildNode(int N) {return (Node)children.get(N);}
  67.         public Text getChildText(int N) {return (Text)children.get(N);}
  68.        
  69.         public void clear() {children.clear();}
  70.         public void deleteChild(int N) {children.remove(N);}
  71.        
  72.         public void setText(String Txt,boolean Preserve)
  73.         {
  74.             clear();
  75.             Text txt=new Text(Txt,Preserve);
  76.             txt.setParent(this);
  77.             children.add(txt);
  78.         }
  79.        
  80.         public String getText()
  81.         {
  82.             String txt="";
  83.             for (int n=0;n<numChildren();n++)
  84.             {
  85.                 if (childType(n)==TYPE_TEXT) txt+=getChildText(n).get();
  86.                 else txt+=getChildNode(n).getText();
  87.             }
  88.             return txt;
  89.         }
  90.        
  91.         public void appendChild(Node Nod) {Nod.setParent(this); children.add(Nod);}
  92.         public void appendChild(Text Txt) {Txt.setParent(this); children.add(Txt);}
  93.         public void insertChild(int N,Node Nod) {Nod.setParent(this); children.add(N,Nod);}
  94.         public void insertChild(int N,Text Txt) {Txt.setParent(this); children.add(N,Txt);}
  95.  
  96.         public Node appendNode(String Name)
  97.         {
  98.             Node nod=new Node(Name);
  99.             nod.setParent(this);
  100.             children.add(nod);
  101.             return nod;
  102.         }
  103.         public Text appendText(String Txt,boolean Preserve)
  104.         {
  105.             Text txt=new Text(Txt,Preserve);
  106.             txt.setParent(this);
  107.             children.add(txt);
  108.             return txt;
  109.         }
  110.     }
  111.    
  112.     class Text
  113.     {
  114.         private Node parentNode=null;
  115.         private String text;
  116.         private boolean preserve; // if true, is CDATA type; otherwise may be freely trimmed for whitespace
  117.    
  118.         public Text(String Text,boolean Preserve) {text=Text; preserve=Preserve;}
  119.  
  120.         public Node parent() {return parentNode;}
  121.         public void setParent(Node Parent) {parentNode=Parent;}
  122.  
  123.         public String get() {return text;}
  124.         public void set(String Txt) {text=Txt;}
  125.         public boolean preserve() {return preserve;}
  126.     }
  127.    
  128.     public Node createNode(String Name) {return new Node(Name);}
  129.     public Text createText(String Text,boolean Preserve) {return new Text(Text,Preserve);}
  130.    
  131.     Node doc=null;
  132.  
  133.     // constructors
  134.    
  135.     public TrivialDOM() {}
  136.  
  137.     public TrivialDOM(String DocName)
  138.     {
  139.         doc=new Node(DocName);
  140.     }
  141.     public TrivialDOM(Node DocNode)
  142.     {
  143.         doc=DocNode;
  144.     }
  145.    
  146.     public Node document() {return doc;}
  147.     public String toString()
  148.     {
  149.         StringWriter out=new StringWriter();
  150.         try {writeXML(out,this);}
  151.         catch (IOException e) {return e.getMessage();}
  152.         return out.toString();
  153.     }
  154.  
  155.     // parsing input files
  156.  
  157.     public static TrivialDOM readXML(BufferedReader in) throws IOException
  158.     {
  159.         final String EOF="ReadXML: unexpected end of file during parsing";
  160.  
  161.         // PART 1: read the input file one character at a time, and carve it up into chunks, which are preserved as strings; these
  162.         // include tag start & end, text, CDATA, and comments.
  163.        
  164.         ArrayList<String> chunks=new ArrayList<String>();
  165.         String str="";
  166.         while (true)
  167.         {
  168.             int ich;
  169.             if (str.length()==0)
  170.             {
  171.                 ich=in.read();
  172.                 if (ich<0) break;
  173.                 str=String.valueOf((char)ich);
  174.             }
  175.            
  176.             if (str.charAt(0)=='<') // either a tag or a CDATA
  177.             {
  178.                 for (int n=0;n<2;n++)
  179.                 {
  180.                     ich=in.read();
  181.                     if (ich<0) throw new IOException(EOF);
  182.                     str=str+(char)ich;
  183.                 }
  184.                
  185.                 if (str.startsWith("<![")) // it's a CDATA
  186.                 {
  187.                     while (true)
  188.                     {
  189.                         ich=in.read();
  190.                         if (ich<0) throw new IOException(EOF);
  191.                         str=str+(char)ich;
  192.                         if (str.endsWith("]]>"))
  193.                         {
  194.                             chunks.add(str);
  195.                             str="";
  196.                             break;
  197.                         }
  198.                     }
  199.                 }
  200.                 else if (str.startsWith("<!-")) // it's a comment
  201.                 {
  202.                     while (true)
  203.                     {
  204.                         ich=in.read();
  205.                         if (ich<0) throw new IOException(EOF);
  206.                         str=str+(char)ich;
  207.                         if (str.endsWith("-->"))
  208.                         {
  209.                             chunks.add(str);
  210.                             str="";
  211.                             break;
  212.                         }
  213.                     }
  214.                 }
  215.                 else // it's an opening tag, which will get closed later
  216.                 {
  217.                     boolean inquot=false;
  218.                     while (true)
  219.                     {
  220.                         ich=in.read();
  221.                         if (ich<0) throw new IOException(EOF);
  222.                         str=str+(char)ich;
  223.                         if ((char)ich=='"') inquot=!inquot;
  224.                         else if ((char)ich=='>')
  225.                         {
  226.                             chunks.add(str);
  227.                             str="";
  228.                             break;
  229.                         }
  230.                     }
  231.                 }
  232.             }
  233.             else // must be plain text
  234.             {
  235.                 boolean eof=false;
  236.                 while (true)
  237.                 {
  238.                     ich=in.read();
  239.                     if (ich<0) {eof=true; break;}
  240.                     if ((char)ich=='<')
  241.                     {
  242.                         chunks.add(str);
  243.                         str=String.valueOf((char)ich);
  244.                         break;
  245.                     }
  246.                     str=str+(char)ich;
  247.                 }
  248.                 if (eof)
  249.                 {
  250.                     if (str.trim().length()==0) break; else throw new IOException(EOF);
  251.                 }
  252.             }
  253.         }
  254.  
  255.         // PART 2: analyze the resulting pieces, and build up the node tree
  256.  
  257.         TrivialDOM xml=new TrivialDOM("unknown");
  258.         Node node=null;
  259.         for (int n=0;n<chunks.size();n++)
  260.         {
  261.             str=chunks.get(n);
  262.             if (str.trim().length()==0) continue; // ignore chunks which are pure whitespace
  263.  
  264.             if (str.charAt(0)=='<' && str.length()>=2 && ((str.charAt(1)>='A' && str.charAt(1)<='Z') ||
  265.                                                           (str.charAt(1)>='a' && str.charAt(1)<='z')) && str.endsWith(">"))
  266.             {
  267.                 str=str.substring(1,str.length()-1);
  268.                 boolean isclosed=str.endsWith("/");
  269.                 if (isclosed) str=str.substring(0,str.length()-1);
  270.                
  271.                 String[] bits=str.split(" ");
  272.                 Node newNode=null;
  273.                 if (node==null)
  274.                 {
  275.                     newNode=xml.document();
  276.                     newNode.setNodeName(bits[0]);
  277.                 }
  278.                 else newNode=node.appendNode(bits[0]);
  279.                
  280.                 for (int i=1;i<bits.length;i++)
  281.                 {
  282.                     int spc=bits[i].indexOf("=");
  283.                     if (spc<=0) throw new IOException("Malformatted attribute: ["+snip(bits[i])+"].");
  284.                     String key=bits[i].substring(0,spc),val=bits[i].substring(spc+1);
  285.                     if (!val.startsWith("\"") || !val.endsWith("\""))
  286.                         throw new IOException("Malformed attribute value: ["+snip(bits[i])+"].");
  287.                     val=val.substring(1,val.length()-1);
  288.                     newNode.setAttribute(key,val);
  289.                 }
  290.                
  291.                 if (!isclosed) node=newNode;
  292.             }
  293.             else if (str.startsWith("</"))
  294.             {
  295.                 if (node==null) throw new IOException("Unexpected end tag: ["+snip(str)+"].");
  296.                 str=str.substring(2,str.length()-1);
  297.                 if (str.compareTo(node.nodeName())!=0)
  298.                     throw new IOException("Closing tag does not match opening tag: ["+snip(str)+"].");
  299.                 node=node.parent();
  300.             }
  301.             else if (str.startsWith("<![CDATA["))
  302.             {
  303.                 if (node==null) throw new IOException("Unexpected CDATA node: ["+snip(str)+"].");
  304.                 if (!str.endsWith("]]>")) throw new IOException("CDATA node not ended: ["+snip(str)+"].");
  305.                 str=str.substring(9,str.length()-3);
  306.                 node.appendText(str,true);
  307.             }
  308.             else if (str.startsWith("<!--"))
  309.             {
  310.                 if (!str.endsWith("-->")) throw new IOException("Unterminated comment: ["+snip(str)+"].");
  311.             }
  312.             else if (str.startsWith("<?")) {} // ignore
  313.             else if (str.startsWith("<")) throw new IOException("Unexpected angle bracket, near: ["+snip(str)+"].");
  314.             else
  315.             {
  316.                 if (node==null) throw new IOException("Misplaced text-like block: ["+snip(str)+"].");
  317.                 node.appendText(unescapeText(str).trim(),false);
  318.             }
  319.         }      
  320.  
  321.         return xml;
  322.     }
  323.    
  324.     // chop a string off if it's too big to go in an exception
  325.     private static String snip(String str)
  326.     {
  327.         if (str.length()<60) return str;
  328.         return str.substring(0,60)+"...";
  329.     }
  330.    
  331.     // writing output files
  332.  
  333.     public static void writeXML(Writer out,TrivialDOM dom) throws IOException
  334.     {
  335.         out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
  336.         recursiveWriteNode(out,dom.document(),0);
  337.         out.flush();
  338.     }
  339.    
  340.     private static void recursiveWriteNode(Writer out,Node nod,int Level) throws IOException
  341.     {
  342.         // emit the node tag & attributes
  343.        
  344.         for (int n=0;n<Level;n++) out.write(" ");
  345.         out.write("<"+nod.nodeName());
  346.         String[] attr=nod.getAttributeNames();
  347.         for (int n=0;n<attr.length;n++) out.write(" "+attr[n]+"=\""+escapeAttr(nod.attribute(attr[n]))+"\"");
  348.  
  349.         // special case for empty nodes
  350.         if (nod.numChildren()==0) {out.write("/>\n"); return;}
  351.  
  352.         out.write(">");
  353.        
  354.         boolean doIndent=true;
  355.         if (nod.numChildren()==1 && nod.childType(0)==TYPE_TEXT) doIndent=false;
  356.         else if (nod.numChildren()>0 && nod.childType(0)==TYPE_TEXT && nod.getChildText(0).preserve()) doIndent=false;
  357.        
  358.         if (doIndent) out.write("\n");
  359.        
  360.         // emit the child nodes
  361.        
  362.         for (int n=0;n<nod.numChildren();n++)
  363.         {
  364.             if (nod.childType(n)==TYPE_TEXT)
  365.             {
  366.                 Text txt=nod.getChildText(n);
  367.                 if (doIndent) for (int i=0;i<Level+1;i++) out.write(" ");
  368.                
  369.                 if (txt.preserve())
  370.                     out.write("<![CDATA["+txt.get()+"]]>");
  371.                 else
  372.                     out.write(escapeText(txt.get()));
  373.                
  374.                 if (doIndent) out.write("\n");
  375.             }
  376.             else recursiveWriteNode(out,nod.getChildNode(n),Level+1);
  377.         }
  378.        
  379.         // emit the closing tag
  380.        
  381.         if (doIndent) for (int n=0;n<Level;n++) out.write(" ");
  382.         out.write("</"+nod.nodeName()+">\n");
  383.     }
  384.    
  385.     // miscellaneous
  386.    
  387.     // make sure a string is suitable to encode in an attribute value (quoted)
  388.     public static String escapeAttr(String S)
  389.     {
  390.         int i;
  391.         while ((i=S.indexOf('"'))>=0) {S=S.substring(0,i)+"&quot;"+S.substring(i+1);}
  392.         while ((i=S.indexOf('\''))>=0) {S=S.substring(0,i)+"&apos;"+S.substring(i+1);}
  393.         return S;
  394.     }
  395.     // make sure a string is suitable for general XML text
  396.     public static String escapeText(String S)
  397.     {
  398.         String str="";
  399.         int i;
  400.         while ((i=S.indexOf('&'))>=0) {str=str+S.substring(0,i)+"&amp;"; S=S.substring(i+1);}
  401.         S=str+S;
  402.         while ((i=S.indexOf('<'))>=0) {S=S.substring(0,i)+"&lt;"+S.substring(i+1);}
  403.         while ((i=S.indexOf('>'))>=0) {S=S.substring(0,i)+"&gt;"+S.substring(i+1);}
  404.         return S;
  405.     }
  406.     // convert any escaped entities back into regular text
  407.     public static String unescapeText(String S)
  408.     {
  409.         String str="";
  410.         int i=0;
  411.         while (i<S.length())
  412.         {
  413.             if (i+5<=S.length() && S.substring(i,i+5).equals("&amp;")) {str+="&"; i+=5;}
  414.             else if (i+4<=S.length() && S.substring(i,i+4).equals("&lt;")) {str+="<"; i+=4;}
  415.             else if (i+4<=S.length() && S.substring(i,i+4).equals("&gt;")) {str+=">"; i+=4;}
  416.             else {str+=S.charAt(i); i++;}
  417.         }
  418.         return str;
  419.     }
  420. }
  421.