/* ****************************************************************************** * * Copyright 2008-2013 Hans Dijkema * * JRichTextEditor is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * JRichTextEditor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with JRichTextEditor. If not, see <http://www.gnu.org/licenses/>. * * ******************************************************************************/ package nl.dykema.jxmlnote.document; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Element; import javax.swing.text.StyleConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import nl.dykema.jxmlnote.exceptions.BadDocumentException; import nl.dykema.jxmlnote.exceptions.BadMetaException; import nl.dykema.jxmlnote.exceptions.DefaultXMLNoteErrorHandler; import nl.dykema.jxmlnote.styles.XMLNoteStyleConstants; import nl.dykema.jxmlnote.utils.DPIAdjuster; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; import org.w3c.dom.DOMException; import org.w3c.dom.Text; public class XMLNoteXMLOut { private XMLNoteDocument _doc; private class MyElement { private AttributeSet _set; private int _start; private int _end; private String _markId; public int getStartOffset() { return _start; } public int getEndOffset() { return _end; } public boolean equalsAttr(Element q) { AttributeSet qs=q.getAttributes(); return qs.isEqual(_set); } /** * precondition: equalsAttr(e) * @param e * @return */ public boolean canBeMerged(Element e) { if ((e.getEndOffset()==_start)) { return true; } if ((e.getStartOffset()==_end)) { return true; } return false; } public AttributeSet getAttributes() { return _set; } public void merge(Element e) { if (e.getEndOffset()==_start) { _start=e.getStartOffset(); } if (e.getStartOffset()==_end) { _end=e.getEndOffset(); } } public MyElement(Element e) { _set=e.getAttributes(); _start=e.getStartOffset(); _end=e.getEndOffset(); } } // converts tabs and newlines to xml elements private void convertTextPartToXML(org.w3c.dom.Document doc,org.w3c.dom.Element xmlElement,String txt,int start,int end) { int i,n,k; for(k=0,i=0,n=txt.length();i<n;i++) { // marks Vector<XMLNoteMark> enders=_doc.getMarksEndingWith(i+start); Vector<XMLNoteMark> starters=_doc.getMarksStartingWith(i+start); Vector<XMLNoteMark> es = new Vector<XMLNoteMark>(); // check if we have enders with the same Id as starters, because we don't want // to get the end --> start situation. We need to first save the start mark // and next the end mark. ==> This is a special case where the mark has // size 0. Hashtable<String,XMLNoteMark> ends = new Hashtable<String,XMLNoteMark>(); { Iterator<XMLNoteMark> it = enders.iterator(); while (it.hasNext()) { XMLNoteMark m = it.next(); ends.put(m.id(),m); } it = starters.iterator(); while (it.hasNext()) { XMLNoteMark m = it.next(); if (ends.get(m.id()) != null) { es.add(m); } } it = es.iterator(); while (it.hasNext()) { XMLNoteMark m = it.next(); enders.remove(m); starters.remove(m); } } // Now output all marks. if ((enders.size()>0) || starters.size()>0 || es.size()>0) { if (k<i) { String s=txt.substring(k,i); k=i; Text node=doc.createTextNode(s); xmlElement.appendChild(node); } Iterator<XMLNoteMark> it=enders.iterator(); while(it.hasNext()) { XMLNoteMark m=it.next(); String c=m.markClass(); String id=m.id(); org.w3c.dom.Element mark=doc.createElement("mark"); mark.setAttribute("type","end"); mark.setAttribute("id", id); if (c!=null) { mark.setAttribute("class", c); } xmlElement.appendChild(mark); } it=starters.iterator(); while(it.hasNext()) { XMLNoteMark m=it.next(); String c=m.markClass(); String id=m.id(); org.w3c.dom.Element mark=doc.createElement("mark"); mark.setAttribute("type","start"); mark.setAttribute("id", id); if (c!=null) { mark.setAttribute("class",c); } xmlElement.appendChild(mark); } it=es.iterator(); while(it.hasNext()) { XMLNoteMark m=it.next(); String c=m.markClass(); String id=m.id(); { org.w3c.dom.Element mark=doc.createElement("mark"); mark.setAttribute("type","start"); mark.setAttribute("id", id); if (c!=null) { mark.setAttribute("class",c); } xmlElement.appendChild(mark); } { org.w3c.dom.Element mark=doc.createElement("mark"); mark.setAttribute("type","end"); mark.setAttribute("id", id); if (c!=null) { mark.setAttribute("class", c); } xmlElement.appendChild(mark); } } } // tabs, enters char chr=txt.charAt(i); if (chr=='\t') { String s=txt.substring(k,i); if (s.length()>0) { Text node=doc.createTextNode(s); xmlElement.appendChild(node); } org.w3c.dom.Element tab=doc.createElement("tab"); xmlElement.appendChild(tab); k=i+1; } else if (chr=='\n') { String s=txt.substring(k,i); if (s.length()>0) { Text node=doc.createTextNode(s); xmlElement.appendChild(node); } org.w3c.dom.Element enter=doc.createElement("enter"); xmlElement.appendChild(enter); k=i+1; } else if (chr==' ') { String s=txt.substring(k,i); if (s.length()>0) { Text node=doc.createTextNode(s); xmlElement.appendChild(node); } org.w3c.dom.Element enter=doc.createElement("space"); xmlElement.appendChild(enter); k=i+1; } } if (k!=i) { String s=txt.substring(k,i); if (s.length()>0) { Text node=doc.createTextNode(s); xmlElement.appendChild(node); } } } // Expected Document structure: See precondition for 'toXML()' // This function exports the contents of the styled paragraphds to XMLNote XML. // It looks for bold, italics, underline and center/right/left/justify attributes to export. // Also it looks if (parts of) the sentence are part of a highlight (XMLNoteMark). private void convertSentenceToXML(org.w3c.dom.Document doc,org.w3c.dom.Element xmlElement,Element parElement) throws BadDocumentException { int n=parElement.getElementCount(); Element e; AttributeSet s; int i; boolean bold,italic,underline; if (n>0) { // first merge elements for as far as possible Vector<MyElement> merged=new Vector<MyElement>(); e=parElement.getElement(0); s=e.getAttributes(); merged.add(new MyElement(e)); for(i=1;i<n;i++) { e=parElement.getElement(i); if (merged.lastElement().equalsAttr(e)) { if (merged.lastElement().canBeMerged(e)) { merged.lastElement().merge(e); } else { merged.add(new MyElement(e)); } } else { merged.add(new MyElement(e)); } } // Next, write them out. for(i=0;i<merged.size();i++) { s=merged.get(i).getAttributes(); if (StyleConstants.getIcon(s)!=null) { // handle XMLNoteImageIcon XMLNoteImageIcon icn=(XMLNoteImageIcon) StyleConstants.getIcon(s); org.w3c.dom.Element img=icn.toXML(doc); xmlElement.appendChild(img); MyElement part=merged.get(i); String txt; try { txt = _doc.getText(part.getStartOffset(),part.getEndOffset()-part.getStartOffset()); } catch (BadLocationException e1) { throw new BadDocumentException(e1.getMessage()); } convertTextPartToXML(doc,img,txt,part.getStartOffset(),part.getEndOffset()); } else { // handle normal text bold=(Boolean) s.getAttribute(StyleConstants.Bold); italic=(Boolean) s.getAttribute(StyleConstants.Italic); underline=(Boolean) s.getAttribute(StyleConstants.Underline); MyElement part=merged.get(i); org.w3c.dom.Element c=xmlElement; if (bold) { org.w3c.dom.Element el=doc.createElement("b"); c.appendChild(el); c=el; } if (italic) { org.w3c.dom.Element el=doc.createElement("i"); c.appendChild(el); c=el; } if (underline) { org.w3c.dom.Element el=doc.createElement("u"); c.appendChild(el); c=el; } String txt; try { txt = _doc.getText(part.getStartOffset(),part.getEndOffset()-part.getStartOffset()); } catch (BadLocationException e1) { throw new BadDocumentException(e1.getMessage()); } convertTextPartToXML(doc,c,txt,part.getStartOffset(),part.getEndOffset()); } } } else { // Write out paragraph without markup try { String txt=_doc.getText(parElement.getStartOffset(),parElement.getEndOffset()-parElement.getStartOffset()); convertTextPartToXML(doc,xmlElement,txt,parElement.getStartOffset(),parElement.getEndOffset()); } catch (BadLocationException e1) { throw new BadDocumentException(e1.getMessage()); } } } // Expected Document structure: See precondition for 'toXML()'. // This function exports the styled paragraphs to XMLNote XML. private void convertParToXML(org.w3c.dom.Document doc,org.w3c.dom.Element xmlElement,Element docElement) throws BadDocumentException { AttributeSet s=docElement.getAttributes(); // style id String styleId=(String) s.getAttribute(XMLNoteStyleConstants.IdAttribute); org.w3c.dom.Element nelement; if (styleId.equals("h1") || styleId.equals("h2") || styleId.equals("h3") || styleId.equals("h4") || styleId.equals("par")) { nelement=doc.createElement(styleId); } else { nelement=doc.createElement("sp"); nelement.setAttribute("style", styleId); } // check alignment int alignment=StyleConstants.getAlignment(s); //Style st=_doc.getStyles().parStyle(styleId).getStyle(DPIAdjuster.DEVICE_NONE); AttributeSet st=_doc.getStyles().parStyle(styleId).getStyle(DPIAdjuster.DEVICE_NONE); int st_alignment=StyleConstants.getAlignment(st); if (alignment!=st_alignment) { String align="left"; if (alignment==StyleConstants.ALIGN_RIGHT) { align="right"; } else if (alignment==StyleConstants.ALIGN_CENTER) { align="center"; } else if (alignment==StyleConstants.ALIGN_JUSTIFIED) { align="justify"; } nelement.setAttribute("align",align); } // check indenting int st_indent=(int) StyleConstants.getLeftIndent(st); int par_indent=(int) StyleConstants.getLeftIndent(s); if (st_indent!=par_indent) { String indent=Integer.toString(par_indent); nelement.setAttribute("indent", indent); } // convert paragraph text to xml convertSentenceToXML(doc,nelement,docElement); // append paragraph xmlElement.appendChild(nelement); } private void createMetaStuff(org.w3c.dom.Document doc,org.w3c.dom.Element meta) { Iterator<String> it=_doc.metaKeys(); while (it.hasNext()) { org.w3c.dom.Element em=doc.createElement("param"); String key=it.next(); em.setAttribute("name", key); try { em.setAttribute("type", _doc.metaType(key)); em.setAttribute("value", _doc.metaValue(key)); } catch (DOMException e) { DefaultXMLNoteErrorHandler.exception(e); } catch (BadMetaException e) { DefaultXMLNoteErrorHandler.exception(e); } meta.appendChild(em); } } // Precondition: XMLNoteDocument exists as follows (sloppy notation): // Doc -- paragraph 1 // paragraph 2 -- tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt (text) // [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] (possible character attributesets) // [MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM] (possible XMLNoteMarks) // ... // paragraph N public String toXML() throws BadDocumentException { String xml; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance (); DocumentBuilder db; db = dbf.newDocumentBuilder (); org.w3c.dom.Document doc = db.newDocument (); org.w3c.dom.Element root = doc.createElement ("xmlnote"); Writer out = new StringWriter(); doc.appendChild (root); root.setAttribute("version", "2010.1"); org.w3c.dom.Element meta=doc.createElement("meta"); createMetaStuff(doc,meta); root.appendChild(meta); org.w3c.dom.Element notes=doc.createElement("notes"); root.appendChild(notes); Element eroot=_doc.getDefaultRootElement(); int nelements=eroot.getElementCount(); int i; for(i=0;i<nelements;i++) { convertParToXML(doc,notes,eroot.getElement(i)); } //Serialize DOM OutputFormat format = new OutputFormat (doc); // as a String XMLSerializer serial = new XMLSerializer (out,format); try { serial.serialize(doc); } catch (IOException e) { e.printStackTrace(); } xml=out.toString(); return xml; } catch (ParserConfigurationException e) { e.printStackTrace(); return null; } } public XMLNoteXMLOut(XMLNoteDocument d) { _doc=d; } }