c6fe0d6212c7c8b983e66a1867374be60c05df2c
[gpl/argeo-suite.git] / publishing / org.argeo.publishing.ui / src / org / argeo / docbook / ui / DbkTextInterpreter.java
1 package org.argeo.docbook.ui;
2
3 import static org.argeo.docbook.DbkType.para;
4 import static org.argeo.docbook.DbkType.title;
5 import static org.argeo.docbook.DbkUtils.isDbk;
6
7 import java.io.ByteArrayInputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.StringReader;
11 import java.nio.charset.StandardCharsets;
12 import java.util.List;
13
14 import javax.jcr.ImportUUIDBehavior;
15 import javax.jcr.Item;
16 import javax.jcr.Node;
17 import javax.jcr.NodeIterator;
18 import javax.jcr.Property;
19 import javax.jcr.PropertyIterator;
20 import javax.jcr.RepositoryException;
21 import javax.xml.parsers.DocumentBuilderFactory;
22
23 import org.apache.commons.io.IOUtils;
24 import org.argeo.docbook.DbkAttr;
25 import org.argeo.docbook.DbkType;
26 import org.argeo.jcr.Jcr;
27 import org.argeo.jcr.JcrException;
28
29 /** Based on HTML with a few Wiki-like shortcuts. */
30 public class DbkTextInterpreter implements TextInterpreter {
31         private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
32
33         @Override
34         public void write(Item item, String content) {
35                 try {
36                         if (item instanceof Node) {
37                                 Node node = (Node) item;
38                                 if (isDbk(node, para) || isDbk(node, title)) {
39                                         String raw = convertToStorage(node, content);
40                                         validateBeforeStoring(raw);
41
42                                         String jcrUuid = node.getIdentifier();
43 //                                      if (node.hasProperty(Property.JCR_UUID))
44 //                                              jcrUuid = node.getProperty(Property.JCR_UUID).getString();
45 //                                      else {
46 //                                              // TODO use time based
47 //                                              jcrUuid = UUID.randomUUID().toString();
48 //                                              node.setProperty(Property.JCR_UUID, jcrUuid);
49 //                                              node.getSession().save();
50 //                                      }
51
52                                         StringBuilder namespaces = new StringBuilder();
53                                         namespaces.append(" xmlns:dbk=\"http://docbook.org/ns/docbook\"");
54                                         namespaces.append(" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\"");
55                                         namespaces.append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
56                                         raw = "<" + node.getName() + " jcr:uuid=\"" + jcrUuid + "\"" + namespaces + ">" + raw + "</"
57                                                         + node.getName() + ">";
58 //                                      System.out.println(raw);
59                                         try (InputStream in = new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8))) {
60                                                 node.getSession().importXML(node.getParent().getPath(), in,
61                                                                 ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
62                                                 // node.getSession().save();
63                                         } catch (IOException e) {
64                                                 throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
65                                         }
66
67 //                                      try {
68 //                                              DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
69 //                                              Document document;
70 //                                              try (Reader in = new StringReader(raw)) {
71 //                                                      document = documentBuilder.parse(new InputSource(in));
72 //                                              }
73 //                                              NodeList nl = document.getChildNodes();
74 //                                              for (int i = 0; i < nl.getLength(); i++) {
75 //                                                      org.w3c.dom.Node n = nl.item(i);
76 //                                                      if (node instanceof Text) {
77 //
78 //                                                      }
79 //                                              }
80 //                                      } catch (ParserConfigurationException | SAXException | IOException e) {
81 //                                              throw new IllegalArgumentException("Cannot parse raw content of " + node, e);
82 //                                      }
83
84 //                                      Node jcrText;
85 //                                      if (!node.hasNode(Jcr.JCR_XMLTEXT))
86 //                                              jcrText = node.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
87 //                                      else
88 //                                              jcrText = node.getNode(Jcr.JCR_XMLTEXT);
89 //                                      jcrText.setProperty(Jcr.JCR_XMLCHARACTERS, raw);
90                                 } else {
91                                         throw new IllegalArgumentException("Don't know how to interpret " + node);
92                                 }
93                         } else {// property
94                                 Property property = (Property) item;
95                                 property.setValue(content);
96                         }
97                         // item.getSession().save();
98                 } catch (RepositoryException e) {
99                         throw new JcrException("Cannot set content on " + item, e);
100                 }
101         }
102
103         @Override
104         public String read(Item item) {
105                 try {
106                         String raw = raw(item);
107                         return convertFromStorage(item, raw);
108                 } catch (RepositoryException e) {
109                         throw new JcrException("Cannot get " + item + " for edit", e);
110                 }
111         }
112
113         @Override
114         public String raw(Item item) {
115                 try {
116                         item.getSession().refresh(true);
117                         if (item instanceof Node) {
118                                 Node node = (Node) item;
119                                 if (isDbk(node, para) || isDbk(node, title)) {
120                                         StringBuilder sb = new StringBuilder();
121                                         readXml(node, sb);
122 //                                      NodeIterator nit = node.getNodes();
123 //                                      while (nit.hasNext()) {
124 //                                              Node child = nit.nextNode();
125 //                                              if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
126 //                                                      Node jcrText = node.getNode(Jcr.JCR_XMLTEXT);
127 //                                                      String txt = jcrText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
128 //                                                      // TODO make it more robust
129 //                                                      // txt = txt.replace("\n", "").replace("\t", "");
130 //                                                      txt = txt.replace("\t", "  ");
131 //                                                      sb.append(txt);
132 //                                              } else {
133 //                                                      try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
134 //                                                              child.getSession().exportDocumentView(child.getPath(), out, true, false);
135 //                                                              sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
136 //                                                      } catch (IOException e) {
137 //                                                              throw new IllegalStateException("Cannot export " + child, e);
138 //                                                      }
139 //                                              }
140 //                                      }
141                                         return sb.toString();
142                                 } else {
143                                         throw new IllegalArgumentException("Don't know how to interpret " + node);
144                                 }
145                         } else {// property
146                                 Property property = (Property) item;
147                                 return property.getString();
148                         }
149                 } catch (RepositoryException e) {
150                         throw new JcrException("Cannot get " + item + " content", e);
151                 }
152         }
153
154         private void readXml(Node node, StringBuilder sb) throws RepositoryException {
155                 NodeIterator nit = node.getNodes();
156                 while (nit.hasNext()) {
157                         Node child = nit.nextNode();
158                         if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
159                                 String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
160                                 // TODO make it more robust
161                                 // txt = txt.replace("\n", "").replace("\t", "");
162                                 txt = txt.replace("\t", "  ");
163                                 sb.append(txt);
164                         } else {
165                                 sb.append('<').append(child.getName());
166                                 PropertyIterator pit = child.getProperties();
167                                 properties: while (pit.hasNext()) {
168                                         Property p = pit.nextProperty();
169                                         if (p.getName().startsWith("jcr:"))
170                                                 continue properties;
171                                         sb.append(' ').append(p.getName()).append("=\"").append(p.getString()).append('\"');
172                                 }
173                                 sb.append('>');
174                                 readXml(child, sb);
175 //                              try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
176 //                                      child.getSession().exportDocumentView(child.getPath(), out, true, false);
177 //                                      sb.append(new String(out.toByteArray(), StandardCharsets.UTF_8));
178 //                              } catch (IOException e) {
179 //                                      throw new IllegalStateException("Cannot export " + child, e);
180 //                              }
181                                 sb.append("</").append(child.getName()).append('>');
182                         }
183                 }
184         }
185
186         private void readAsSimpleHtml(Node node, StringBuilder sb) throws RepositoryException {
187                 NodeIterator nit = node.getNodes();
188                 while (nit.hasNext()) {
189                         Node child = nit.nextNode();
190                         if (child.getName().equals(Jcr.JCR_XMLTEXT)) {
191                                 String txt = child.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
192                                 // TODO make it more robust
193                                 // txt = txt.replace("\n", "").replace("\t", "");
194                                 txt = txt.replace("\t", "  ");
195                                 String html = textToSimpleHtml(txt);
196                                 sb.append(html);
197                         } else if (child.getName().equals(DbkType.link.get())) {
198                                 if (child.hasProperty(DbkAttr.XLINK_HREF)) {
199                                         String href = child.getProperty(DbkAttr.XLINK_HREF).getString();
200                                         // TODO deal with other forbidden XML characters?
201                                         href = href.replace("&", "&amp;");
202                                         sb.append("<a href=\"").append(href).append("\">");
203                                         readAsSimpleHtml(child, sb);
204                                         sb.append("</a>");
205                                 }
206                         } else {
207                                 // ignore
208                         }
209                 }
210         }
211
212         private String textToSimpleHtml(String raw) {
213                 // FIXME the saved data should be corrected instead.
214                 if (raw.indexOf('&') >= 0) {
215                         raw = raw.replace("&", "&amp;");
216                 }
217                 if (raw.indexOf('<') >= 0) {
218                         raw = raw.replace("<", "&lt;");
219                 }
220                 if (raw.indexOf('>') >= 0) {
221                         raw = raw.replace(">", "&gt;");
222                 }
223                 if (raw.indexOf('\"') >= 0) {
224                         raw = raw.replace("\"", "&quot;");
225                 }
226                 if (raw.indexOf('\'') >= 0) {
227                         raw = raw.replace("\'", "&apos;");
228                 }
229 //              raw = "<span style='text-align:justify'>" + raw + "</span>";
230                 if (raw.length() == 0)
231                         return raw;
232                 try (StringReader reader = new StringReader(raw)) {
233                         List<String> lines = IOUtils.readLines(reader);
234                         if (lines.size() == 1)
235                                 return lines.get(0);
236                         StringBuilder sb = new StringBuilder(raw.length() + lines.size() * BR_LENGTH);
237                         for (int i = 0; i < lines.size(); i++) {
238                                 if (i != 0)
239                                         sb.append("<br/>");
240                                 sb.append(lines.get(i));
241                         }
242                         return sb.toString();
243                 } catch (IOException e) {
244                         throw new RuntimeException(e);
245                 }
246         }
247
248         final static int BR_LENGTH = "<br/>".length();
249
250         public String readSimpleHtml(Item item) {
251                 try {
252                         StringBuilder sb = new StringBuilder();
253                         readAsSimpleHtml((Node) item, sb);
254                         return sb.toString();
255                 } catch (RepositoryException e) {
256                         throw new JcrException("Cannot convert " + item + " to simple HTML", e);
257                 }
258         }
259
260         // EXTENSIBILITY
261         /**
262          * To be overridden, in order to make sure that only valid strings are being
263          * stored.
264          */
265         protected void validateBeforeStoring(String raw) {
266         }
267
268         /** To be overridden, in order to support additional formatting. */
269         protected String convertToStorage(Item item, String content) throws RepositoryException {
270                 return content;
271
272         }
273
274         /** To be overridden, in order to support additional formatting. */
275         protected String convertFromStorage(Item item, String content) throws RepositoryException {
276                 return content;
277         }
278 }