/* ******************************************************************************
*
* Copyright 2008-2010 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.html;
import java.awt.Color;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.Stack;
import java.util.Vector;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import nl.dykema.jxmlnote.document.XMLNoteDocument;
import nl.dykema.jxmlnote.document.XMLNoteMark;
import nl.dykema.jxmlnote.exceptions.BadDocumentException;
import nl.dykema.jxmlnote.exceptions.BadMetaException;
import nl.dykema.jxmlnote.exceptions.BadStyleException;
import nl.dykema.jxmlnote.exceptions.DefaultXMLNoteErrorHandler;
import nl.dykema.jxmlnote.interfaces.MarkMarkupProvider;
import nl.dykema.jxmlnote.interfaces.MarkMarkupProviderMaker;
import nl.dykema.jxmlnote.resources.XMLNoteResource;
import nl.dykema.jxmlnote.styles.XMLNoteParStyle;
import nl.dykema.jxmlnote.styles.XMLNoteStyleIdConverter;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* This class can be used to transform XMLNote XML to Html.
*
* @author Hans Dijkema
*/
public class XMLNoteToHtml {
XMLNoteDocument _doc;
String _xml;
Writer _html;
String _css;
/**
* This method converts an <code>XMLNoteDocument</code> to <code>HTML</code>, using the given <code>MarkMarkupProvider</code>,
* which may be <b>null</b>. If the <code>MarkMarkupProvider</code> is <b>null</b>, no marks will be exported.
* <p>
* @param doc The document to convert (note: <code>doc.getPart()</code>, will get a selection).
* @param maker The optional MarkMarkupProviderMaker.
* @return Returns the export <code>HTML</code>
* @throws BadDocumentException
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public static String toString(XMLNoteDocument doc,MarkMarkupProviderMaker maker) throws BadDocumentException, IOException, SAXException, ParserConfigurationException {
StringWriter html=new StringWriter();
convert(doc,maker,html);
String h=html.toString();
return h;
}
/**
* This method converts an <code>XMLNoteDocument</code> to <code>XHTML</code> and writes the output to the given <code>Writer</code>.
* The given <code>MarkMarkupProvider</code> is used, which may be <b>null</b>. If the <code>MarkMarkupProvider</code>
* is <b>null</b>, no marks will be exported.
* <p>
* @param doc The document to convert (note: <code>doc.getPart()</code>, will get a selection).
* @param maker The optional MarkMarkupProviderMaker.
* @param html The writer to write the XHTML to.
* @throws BadDocumentException
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public static void convert(XMLNoteDocument doc,MarkMarkupProviderMaker maker,Writer html) throws BadDocumentException, IOException, SAXException, ParserConfigurationException {
String xml=doc.toXML();
String css=doc.getStyles().asCSS(new XMLNoteStyleIdConverter() {
public String convert(String id) {
if (id.equals("h1") || id.equals("h2") || id.equals("h3") || id.equals("h4")) {
return id;
} else {
return "p."+id;
}
}
});
XMLNoteToHtml obj=new XMLNoteToHtml(doc,xml,html,css);
obj.toHtml(new StringReader(xml),maker);
}
private void toHtml(Reader rd,MarkMarkupProviderMaker maker) throws IOException, SAXException, ParserConfigurationException {
SAXParserFactory factory=SAXParserFactory.newInstance();
SAXParser parser=factory.newSAXParser();
XMLReader reader=parser.getXMLReader();
XMLNoteContentHandler handler=new XMLNoteContentHandler(this,maker);
reader.setContentHandler(handler);
InputSource source=new InputSource(rd);
reader.parse(source);
handler.toHtml();
}
protected XMLNoteToHtml(XMLNoteDocument doc,String xml,Writer html,String css) {
_doc=doc;
_xml=xml;
_html=html;
_css=css;
}
}
class XMLNoteContentHandler extends DefaultHandler {
class Mark extends XMLNoteMark {
public String content() {
return null;
}
public Integer offset() {
return -1;
}
public Integer endOffset() {
return -1;
}
public Mark(String id,String c) {
super(id,c);
}
}
XMLNoteToHtml _cvt;
org.w3c.dom.Document _html;
boolean _includeMarks;
MarkMarkupProviderMaker _maker;
Stack<org.w3c.dom.Element> _elemStack;
Stack<Mark> _markStack;
StringBuffer _buffer;
XMLNoteParStyle _currentStyle;
public void toHtml() {
OutputFormat format=new OutputFormat(_html);
format.setOmitDocumentType(false);
format.setOmitXMLDeclaration(true);
XMLSerializer serial=new XMLSerializer(_cvt._html,format);
try {
serial.serialize(_html);
} catch (IOException e) {
DefaultXMLNoteErrorHandler.exception(e);
}
}
public XMLNoteContentHandler(XMLNoteToHtml cvt,MarkMarkupProviderMaker maker) throws ParserConfigurationException {
_cvt=cvt;
_currentStyle=null;
_maker=maker;
_includeMarks=(_maker!=null);
_markStack=new Stack<Mark>();
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
DocumentBuilder db=dbf.newDocumentBuilder();
db.setEntityResolver(new EntityResolver() {
public InputSource resolveEntity(java.lang.String publicId, java.lang.String systemId)
throws SAXException, java.io.IOException {
return new InputSource(XMLNoteResource.get("xhtml4-strict.dtd"));
}
});
DOMImplementation impl=db.getDOMImplementation();
DocumentType xmlDocType = impl.createDocumentType("html", "-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/html4/strict.dtd");
_html=impl.createDocument(null,"html", xmlDocType);
XMLNoteDocument doc=_cvt._doc;
org.w3c.dom.Element root=_html.getDocumentElement();
org.w3c.dom.Element head=_html.createElement("head");
root.appendChild(head);
// Character set UTF-8
Element charset=_html.createElement("meta");
charset.setAttribute("http-equiv", "content-type");
charset.setAttribute("content", "text/html; charset=UTF-8");
head.appendChild(charset);
// meta information
try {
Iterator<String> it=doc.metaKeys();
while (it.hasNext()) {
String key=it.next();
String value=doc.metaValue(key);
String type=doc.metaType(key);
org.w3c.dom.Element meta=_html.createElement("meta");
meta.setAttribute("name", key);
meta.setAttribute("content", type+";"+value);
head.appendChild(meta);
}
} catch (BadMetaException E) { // Unexpected.
DefaultXMLNoteErrorHandler.exception(E);
}
// style sheet
Element style=_html.createElement("style");
style.setAttribute("type","text/css");
head.appendChild(style);
style.setTextContent(_cvt._css);
// marks
Vector<XMLNoteMark> marks=doc.getOrderedMarks();
Iterator<XMLNoteMark> it=marks.iterator();
while (it.hasNext()) {
Element meta=_html.createElement("meta");
XMLNoteMark m=it.next();
meta.setAttribute("name", "mark:"+m.id());
meta.setAttribute("content", "offset="+m.offset()+";endOffset="+m.endOffset()+";class="+((m.markClass()==null) ? "" : m.markClass()));
head.appendChild(meta);
}
// body
org.w3c.dom.Element body=_html.createElement("body");
root.appendChild(body);
_elemStack=new Stack<org.w3c.dom.Element>();
_elemStack.push(body);
}
private void createPar(String tag,String _class,Attributes attrs,XMLNoteParStyle style) {
createNode(tag,_class,attrs);
_currentStyle=style;
}
private void createNode(String tag,Attributes attrs) {
createNode(tag,null,attrs);
}
private void createNode(String tag,String _class,Attributes attrs) {
textOut();
org.w3c.dom.Element el=_html.createElement(tag);
if (_class!=null) {
el.setAttribute("class", _class);
}
String align=attrs.getValue("align");
String indent=attrs.getValue("indent");
String style="";
if (align!=null) { style+="text-align:"+align+";"; }
if (indent!=null) { style+="text-indent:"+indent+"pt;"; }
if (!style.equals("")) {
el.setAttribute("style", style);
}
_elemStack.peek().appendChild(el);
_elemStack.push(el);
_buffer=new StringBuffer();
}
private void addText(String s) {
_buffer.append(s);
}
private String htmlColor(Color c) {
return String.format("#%02x%02x%02x",c.getRed(),c.getGreen(),c.getBlue());
}
private void textOut() {
if (_buffer!=null) {
if (_buffer.length()>0) {
String s=_buffer.toString();
Text txtnode=_html.createTextNode(s);
if (_markStack.isEmpty()) {
_elemStack.peek().appendChild(txtnode);
} else { // this implies _includeMarks
Element em=_html.createElement("span");
Mark mm=_markStack.peek();
MarkMarkupProvider _provider=_maker.create(mm.id(),mm.markClass());
Color bg=_provider.markColor(mm);
MarkMarkupProvider.MarkupType type=_provider.type(mm);
if (type==MarkMarkupProvider.MarkupType.MARKER) {
em.setAttribute("style", "background-color:"+htmlColor(bg)+";");
} else {
em.setAttribute("style", "border-bottom: 2px solid "+htmlColor(bg)+";");
}
if (mm.markClass()==null) {
em.setAttribute("id", "id="+mm.id());
} else {
em.setAttribute("id", "id="+mm.id()+";class="+mm.markClass());
}
em.setAttribute("class","mark");
_elemStack.peek().appendChild(em);
em.appendChild(txtnode);
}
_buffer=new StringBuffer();
}
}
}
private XMLNoteParStyle getStyle(String styleId) {
XMLNoteParStyle st=_cvt._doc.getStyles().parStyle(styleId);
if (st==null) {
try {
return _cvt._doc.getStyles().getDefaultStyle();
} catch (BadStyleException e) {
DefaultXMLNoteErrorHandler.exception(e);
return null;
}
} else {
return st;
}
}
private void endPar() {
textOut();
// process nodes of curtyperent paragraph for tabs.
Element par=_elemStack.pop();
// postprocess stuff
NodeList nodes=par.getElementsByTagName("tab");
if (nodes.getLength()>0) {
if (!_elemStack.peek().getTagName().equals("table")) {
_elemStack.peek().removeChild(par);
Element table=_html.createElement("table");
_elemStack.peek().appendChild(table);
_elemStack.push(table);
} else {
_elemStack.peek().removeChild(par);
}
Element table=_elemStack.peek();
NodeList parNodes=par.getChildNodes();
int i,n;
Vector<Node> v=new Vector<Node>();
for(i=0,n=parNodes.getLength();i<n;i++) {
v.add(parNodes.item(i));
}
Iterator<Node> it=v.iterator();
while (it.hasNext()) {
Node nd=it.next();
par.removeChild(nd);
}
Element row=_html.createElement("tr");
table.appendChild(row);
Element td=_html.createElement("td");
Element pp=(Element) par.cloneNode(false);
int tabCount=0;
TabSet tabs=null;
if (_currentStyle!=null) {
tabs=_currentStyle.tabs();
}
TabStop stop=null;
if (tabs!=null) {
if (tabCount<tabs.getTabCount()) {
stop=tabs.getTab(tabCount);
}
}
row.appendChild(td);
if (stop!=null) {
int a=stop.getAlignment();
String aa="left";
if (a==TabStop.ALIGN_RIGHT) { aa="right"; }
else if (a==TabStop.ALIGN_CENTER) { aa="center"; }
else if (a==TabStop.ALIGN_DECIMAL) { aa="right"; }
td.setAttribute("align",aa);
}
td.appendChild(pp);
it=v.iterator();
while (it.hasNext()) {
Node ee=it.next();
if (ee instanceof Element) {
if (((Element) ee).getTagName().equals("tab")) {
tabCount+=1;
td=_html.createElement("td");
pp=(Element) par.cloneNode(false);
row.appendChild(td);
td.appendChild(pp);
stop=null;
if (tabs!=null) {
if (tabCount<tabs.getTabCount()) {
stop=tabs.getTab(tabCount);
}
}
if (stop!=null) {
int a=stop.getAlignment();
String aa="left";
if (a==TabStop.ALIGN_RIGHT) { aa="right"; }
else if (a==TabStop.ALIGN_CENTER) { aa="center"; }
else if (a==TabStop.ALIGN_DECIMAL) { aa="right"; }
td.setAttribute("align",aa);
}
} else {
pp.appendChild(ee);
}
} else {
pp.appendChild(ee);
}
}
} else { // end of tabs
if (_elemStack.peek().getTagName().equals("table")) {
Element table=_elemStack.pop();
table.removeChild(par);
_elemStack.peek().appendChild(par);
}
}
_currentStyle=null;
}
private void endNode() {
textOut();
_elemStack.pop();
}
private void createTabNode(String tag,Attributes attrs) {
textOut();
Stack<Element> st=new Stack<Element>();
String tg=_elemStack.peek().getTagName();
while (tg.equals("b") || tg.equals("i") || tg.equals("u")) {
st.push(_elemStack.pop());
tg=_elemStack.peek().getTagName();
}
_elemStack.peek().appendChild(_html.createElement("tab"));
while (!st.isEmpty()) {
Element n=(Element) st.pop().cloneNode(false);
_elemStack.peek().appendChild(n);
_elemStack.push(n);
}
}
private void createImageNode(String tag,Attributes attrs) {
textOut();
Stack<Element> st=new Stack<Element>();
String tg=_elemStack.peek().getTagName();
while (tg.equals("b") || tg.equals("i") || tg.equals("u")) {
st.push(_elemStack.pop());
tg=_elemStack.peek().getTagName();
}
Element img=_html.createElement("img");
String description=attrs.getValue("description");
if (description!=null) { img.setAttribute("alt",description); }
String style="";
String width_in_pt=attrs.getValue("width_in_pt");
if (width_in_pt!=null) {
double w=Integer.parseInt(width_in_pt)*2.54/72.0;
style+="width:"+w+"cm;";
}
String height_in_pt=attrs.getValue("height_in_pt");
if (height_in_pt!=null) {
double h=Integer.parseInt(height_in_pt)*2.54/72.0;
style+="height:"+h+"cm;";
}
String url=attrs.getValue("url");
if (url!=null) { img.setAttribute("src",url); }
if (!style.equals("")) { img.setAttribute("style", style); }
_elemStack.peek().appendChild(img);
while (!st.isEmpty()) {
Element n=(Element) st.pop().cloneNode(false);
_elemStack.peek().appendChild(n);
_elemStack.push(n);
}
}
private void processMark(Attributes attrs) {
if (_includeMarks) {
textOut();
String id=attrs.getValue("id");
String type=attrs.getValue("type");
String _class=attrs.getValue("class");
if (type.equals("start")) {
Mark m=new Mark(id,_class);
_markStack.push(m);
} else {
_markStack.pop();
}
}
}
public void startElement(String uri, String localName,String qName,Attributes attrs) throws SAXException {
if (qName.equals("xmlnote") || qName.equals("meta") || qName.equals("param") || qName.equals("notes")) {
// do nothing
} else if (qName.equals("par")) { createPar("p","par",attrs,getStyle("par")); }
else if (qName.equals("h1")) { createPar("h1",null,attrs,getStyle("h1")); }
else if (qName.equals("h2")) { createPar("h2",null,attrs,getStyle("h2")); }
else if (qName.equals("h3")) { createPar("h3",null,attrs,getStyle("h3")); }
else if (qName.equals("h4")) { createPar("h4",null,attrs,getStyle("h4")); }
else if (qName.equals("sp")) { createPar("p",attrs.getValue("style"),attrs,getStyle(attrs.getValue("style"))); }
else if (qName.equals("b")) { createNode("b",attrs); }
else if (qName.equals("i")) { createNode("i",attrs); }
else if (qName.equals("u")) { createNode("u",attrs); }
else if (qName.equals("image")) { createImageNode("img",attrs); }
else if (qName.equals("space")) { addText(" "); }
else if (qName.equals("tab")) { createTabNode("tab",attrs); }
else if (qName.equals("enter")) { addText("\n"); }
else if (qName.equals("mark")) {
processMark(attrs);
}
else {
throw new SAXException("Unknown tag: "+qName);
}
}
public void endElement(String uri,String localName,String qName) throws SAXException {
if (qName.equals("notes")) {
_elemStack.pop(); // pop the body.
} else if (qName.equals("par")) { endPar(); }
else if (qName.equals("h1")) { endPar(); }
else if (qName.equals("h2")) { endPar(); }
else if (qName.equals("h3")) { endPar(); }
else if (qName.equals("h4")) { endPar(); }
else if (qName.equals("sp")) { endPar(); }
else if (qName.equals("b")) { endNode(); }
else if (qName.equals("i")) { endNode(); }
else if (qName.equals("u")) { endNode(); }
else if (qName.equals("image")) {
// do nothing
} else if (qName.equals("space")) {
// do nothing
} else if (qName.equals("tab")) {
// do nothing
} else if (qName.equals("enter")) {
// do nothing
} else if (qName.equals("mark")) {
// do nothing
}
}
public void characters(char [] ch,int start,int length) throws SAXException {
_buffer.append(new String(ch,start,length));
}
}