1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.metis.viewer;
18
19 import io.github.tonywasher.joceanus.metis.data.MetisDataDelta;
20 import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
21 import io.github.tonywasher.joceanus.metis.exc.MetisIOException;
22 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
23 import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
24 import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
25 import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
26 import org.w3c.dom.Document;
27 import org.w3c.dom.Element;
28
29 import javax.xml.XMLConstants;
30 import javax.xml.parsers.DocumentBuilder;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.transform.Transformer;
33 import javax.xml.transform.TransformerException;
34 import javax.xml.transform.TransformerFactory;
35 import javax.xml.transform.dom.DOMSource;
36 import javax.xml.transform.stream.StreamResult;
37 import java.io.StringWriter;
38
39
40
41
42 public class MetisViewerBuilder {
43
44
45
46 private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(MetisViewerBuilder.class);
47
48
49
50
51 private static final int WRAP_HEXSTRING = 60;
52
53
54
55
56 private static final String ELEMENT_HTML = "html";
57
58
59
60
61 private static final String ELEMENT_BODY = "body";
62
63
64
65
66 private static final String ELEMENT_TITLE = "h2";
67
68
69
70
71 private static final String ELEMENT_LINK = "a";
72
73
74
75
76 private static final String ELEMENT_TABLE = "table";
77
78
79
80
81 private static final String ELEMENT_THEAD = "thead";
82
83
84
85
86 private static final String ELEMENT_TBODY = "tbody";
87
88
89
90
91 private static final String ELEMENT_TROW = "tr";
92
93
94
95
96 private static final String ELEMENT_THDR = "th";
97
98
99
100
101 private static final String ELEMENT_TCELL = "td";
102
103
104
105
106 private static final String ATTR_CLASS = "class";
107
108
109
110
111 private static final String ATTR_HREF = "href";
112
113
114
115
116 private static final String CLASS_VIEWER = "-metis-viewer";
117
118
119
120
121 private static final String CLASS_ODDROW = "-metis-oddrow";
122
123
124
125
126 private static final String CLASS_EVENROW = "-metis-evenrow";
127
128
129
130
131 private static final String CLASS_CHANGED = "-metis-changed";
132
133
134
135
136 private static final String CLASS_SECCHANGED = "-metis-security";
137
138
139
140
141 private static final String BLANK_LINK = "<Blank>";
142
143
144
145
146 private final OceanusDataFormatter theFormatter;
147
148
149
150
151 private final Transformer theXformer;
152
153
154
155
156 private final Document theDocument;
157
158
159
160
161 private final Element theBody;
162
163
164
165
166 private MetisViewerPage thePage;
167
168
169
170
171 private Element theTblHdr;
172
173
174
175
176 private Element theTblBody;
177
178
179
180
181 private Element theTblRow;
182
183
184
185
186 private int theNumRows;
187
188
189
190
191
192
193
194 protected MetisViewerBuilder(final OceanusDataFormatter pFormatter) throws OceanusException {
195
196 try {
197
198 theFormatter = pFormatter;
199
200
201 final DocumentBuilderFactory myDocFactory = DocumentBuilderFactory.newInstance();
202 myDocFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
203 myDocFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
204 myDocFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
205
206
207 final DocumentBuilder myBuilder = myDocFactory.newDocumentBuilder();
208
209
210 final TransformerFactory myXformFactory = TransformerFactory.newInstance();
211 myXformFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
212 myXformFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
213 myXformFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
214 theXformer = myXformFactory.newTransformer();
215
216
217 theDocument = myBuilder.newDocument();
218
219
220 final Element myHtml = theDocument.createElement(ELEMENT_HTML);
221 theDocument.appendChild(myHtml);
222 theBody = theDocument.createElement(ELEMENT_BODY);
223 myHtml.appendChild(theBody);
224
225 } catch (Exception e) {
226 throw new MetisIOException("Failed to create", e);
227 }
228 }
229
230
231
232
233 protected void formatDocument() {
234
235 try {
236
237 final StringWriter myWriter = new StringWriter();
238 theXformer.transform(new DOMSource(theDocument), new StreamResult(myWriter));
239
240
241 thePage.setHtml(myWriter.toString());
242
243 } catch (TransformerException e) {
244 LOGGER.error("Failed to format document", e);
245 }
246 }
247
248
249
250
251
252
253 private static void clearElement(final Element pElement) {
254
255 while (pElement.hasChildNodes()) {
256 pElement.removeChild(pElement.getFirstChild());
257 }
258 }
259
260
261
262
263
264
265 protected void resetDocument(final MetisViewerPage pPage) {
266
267 thePage = pPage;
268 thePage.resetPage();
269
270
271 clearElement(theBody);
272 }
273
274
275
276
277
278
279 protected void newTitle(final String pTitle) {
280
281 final Element myTitle = theDocument.createElement(ELEMENT_TITLE);
282 theBody.appendChild(myTitle);
283 myTitle.setTextContent(pTitle);
284 }
285
286
287
288
289 protected void newTable() {
290
291 final Element myTable = theDocument.createElement(ELEMENT_TABLE);
292 myTable.setAttribute(ATTR_CLASS, CLASS_VIEWER);
293 theBody.appendChild(myTable);
294 final Element myHead = theDocument.createElement(ELEMENT_THEAD);
295 myTable.appendChild(myHead);
296 theTblHdr = theDocument.createElement(ELEMENT_TROW);
297 myHead.appendChild(theTblHdr);
298 theTblBody = theDocument.createElement(ELEMENT_TBODY);
299 myTable.appendChild(theTblBody);
300
301
302 theTblRow = null;
303 theNumRows = 0;
304 }
305
306
307
308
309
310
311 protected void newTitleCell(final String pTitle) {
312
313 final Element myCell = theDocument.createElement(ELEMENT_THDR);
314 theTblHdr.appendChild(myCell);
315 myCell.setTextContent(pTitle);
316 }
317
318
319
320
321 protected void newTableRow() {
322
323 theTblRow = theDocument.createElement(ELEMENT_TROW);
324 theTblBody.appendChild(theTblRow);
325
326
327 theTblRow.setAttribute(ATTR_CLASS, (theNumRows % 2 == 0)
328 ? CLASS_EVENROW
329 : CLASS_ODDROW);
330 theNumRows++;
331 }
332
333
334
335
336
337
338 protected void newDataCell(final Object pData) {
339 newDataCell(pData, false);
340 }
341
342
343
344
345
346
347
348 protected void newDataCell(final Object pData,
349 final boolean pChanged) {
350
351 final Element myCell = theDocument.createElement(ELEMENT_TCELL);
352 theTblRow.appendChild(myCell);
353
354
355 String myText = formatValue(pData);
356
357
358 if (pData instanceof MetisDataDelta myDelta) {
359
360 final MetisDataDifference myDiff = myDelta.getDifference();
361
362
363 if (!myDiff.isIdentical()) {
364 myCell.setAttribute(ATTR_CLASS, myDiff.isValueChanged()
365 ? CLASS_CHANGED
366 : CLASS_SECCHANGED);
367 }
368 } else if (pChanged) {
369 myCell.setAttribute(ATTR_CLASS, CLASS_CHANGED);
370 }
371
372
373 if (MetisViewerPage.isLinkable(pData)) {
374
375 final Element myLink = theDocument.createElement(ELEMENT_LINK);
376 myCell.appendChild(myLink);
377 myLink.setAttribute(ATTR_HREF, thePage.newLink(pData));
378 myText = myText.isEmpty() ? BLANK_LINK : myText;
379 myLink.setTextContent(myText);
380
381
382 } else {
383 myCell.setTextContent(myText);
384 }
385 }
386
387
388
389
390
391
392
393 private String formatValue(final Object pValue) {
394
395 String myFormat = theFormatter.formatObject(pValue);
396
397
398 if (needsWrapping(pValue)
399 && (myFormat.length() > WRAP_HEXSTRING)) {
400 final StringBuilder myBuffer = new StringBuilder(myFormat.length() << 1);
401
402
403 myBuffer.append(myFormat);
404
405
406 int iCount = myFormat.length()
407 / WRAP_HEXSTRING;
408 while (iCount > 0) {
409 myBuffer.insert(WRAP_HEXSTRING
410 * iCount--, '\n');
411 }
412
413
414 myFormat = myBuffer.toString();
415 }
416
417
418 return myFormat;
419 }
420
421
422
423
424
425
426
427 private static boolean needsWrapping(final Object pObject) {
428
429 Object myObject = pObject;
430 if (myObject instanceof MetisDataDelta) {
431 myObject = ((MetisDataDelta) pObject).getObject();
432 }
433 return myObject instanceof byte[];
434 }
435 }