1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.plugin.feed

File SyndEntryDocumentSource.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart7.png
64% of files have more coverage

Code metrics

90
243
45
2
765
504
96
0.4
5.4
22.5
2.13

Classes

Class Line # Actions
SyndEntryDocumentSource 56 227 0% 90 137
0.612994461.3%
SyndEntryDocumentSource.PropertySelector 61 16 0% 6 8
0.666666766.7%
 

Contributing tests

This file is covered by 8 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.plugin.feed;
21   
22    import java.io.ByteArrayInputStream;
23    import java.io.ByteArrayOutputStream;
24    import java.io.PrintWriter;
25    import java.nio.charset.Charset;
26    import java.util.ArrayList;
27    import java.util.Collections;
28    import java.util.Date;
29    import java.util.HashMap;
30    import java.util.List;
31    import java.util.Map;
32    import java.util.Properties;
33   
34    import org.apache.commons.io.output.NullWriter;
35    import org.slf4j.Logger;
36    import org.slf4j.LoggerFactory;
37    import org.w3c.dom.Node;
38    import org.w3c.tidy.Tidy;
39    import org.xwiki.xml.XMLUtils;
40   
41    import com.sun.syndication.feed.synd.SyndCategory;
42    import com.sun.syndication.feed.synd.SyndCategoryImpl;
43    import com.sun.syndication.feed.synd.SyndContent;
44    import com.sun.syndication.feed.synd.SyndContentImpl;
45    import com.sun.syndication.feed.synd.SyndEntry;
46    import com.xpn.xwiki.XWiki;
47    import com.xpn.xwiki.XWikiContext;
48    import com.xpn.xwiki.XWikiException;
49    import com.xpn.xwiki.api.Document;
50    import com.xpn.xwiki.doc.XWikiDocument;
51    import com.xpn.xwiki.util.TidyMessageLogger;
52   
53    /**
54    * Concrete strategy for computing the field values of a feed entry from any {@link XWikiDocument} instance.
55    */
 
56    public class SyndEntryDocumentSource implements SyndEntrySource
57    {
58    /**
59    * Utility class for selecting a property from a XWiki object.
60    */
 
61    public static class PropertySelector
62    {
63    /**
64    * The name of a XWiki class.
65    */
66    private String className;
67   
68    /**
69    * The index of an object within the document that this selector is applied to.
70    */
71    private int objectIndex;
72   
73    /**
74    * The name of a property available for {@link #className}.
75    */
76    private String propertyName;
77   
78    /**
79    * Creates a new instance from a string representation.
80    *
81    * @param strRep a string like "ClassName_ObjectIndex_PropertyName", where class name and object index are
82    * optional
83    */
 
84  3 toggle public PropertySelector(String strRep)
85    {
86  3 int indexStartPos = strRep.indexOf('_');
87  3 if (indexStartPos < 0) {
88    // class name and object index are not specified
89  0 this.className = null;
90  0 this.objectIndex = 0;
91  0 this.propertyName = strRep;
92    } else {
93  3 int propStartPos = strRep.indexOf("_", indexStartPos + 1);
94  3 if (propStartPos < 0) {
95    // object index is not specified
96  3 this.className = strRep.substring(0, indexStartPos);
97  3 this.objectIndex = 0;
98  3 this.propertyName = strRep.substring(indexStartPos + 1);
99    } else {
100    // all three have been specified
101  0 this.className = strRep.substring(0, indexStartPos);
102  0 this.objectIndex = Integer.parseInt(strRep.substring(indexStartPos + 1, propStartPos));
103  0 this.propertyName = strRep.substring(propStartPos + 1);
104    }
105    }
106    }
107   
108    /**
109    * @return the name of a XWiki class
110    */
 
111  6 toggle public String getClassName()
112    {
113  6 return this.className;
114    }
115   
116    /**
117    * @return the index of an object within the document that this selector is applied to
118    */
 
119  3 toggle public int getObjectIndex()
120    {
121  3 return this.objectIndex;
122    }
123   
124    /**
125    * @return the name of a property available for {@link #className}
126    */
 
127  3 toggle public String getPropertyName()
128    {
129  3 return this.propertyName;
130    }
131    }
132   
133    protected static final Logger LOGGER = LoggerFactory.getLogger(SyndEntryDocumentSource.class);
134   
135    protected static final TidyMessageLogger TIDY_LOGGER = new TidyMessageLogger(LOGGER);
136   
137    public static final String CONTENT_TYPE = "ContentType";
138   
139    public static final String CONTENT_LENGTH = "ContentLength";
140   
141    public static final Properties TIDY_FEED_CONFIG;
142   
143    public static final Properties TIDY_XML_CONFIG;
144   
145    public static final Properties TIDY_HTML_CONFIG;
146   
147    public static final String FIELD_URI = "uri";
148   
149    public static final String FIELD_LINK = "link";
150   
151    public static final String FIELD_TITLE = "title";
152   
153    public static final String FIELD_DESCRIPTION = "description";
154   
155    public static final String FIELD_CATEGORIES = "categories";
156   
157    public static final String FIELD_PUBLISHED_DATE = "publishedDate";
158   
159    public static final String FIELD_UPDATED_DATE = "updatedDate";
160   
161    public static final String FIELD_AUTHOR = "author";
162   
163    public static final String FIELD_CONTRIBUTORS = "contributors";
164   
165    public static final Map<String, Object> DEFAULT_PARAMS;
166   
 
167  1 toggle static {
168    // general configuration
169  1 TIDY_FEED_CONFIG = new Properties();
170  1 TIDY_FEED_CONFIG.setProperty("force-output", "yes");
171  1 TIDY_FEED_CONFIG.setProperty("indent-attributes", "no");
172  1 TIDY_FEED_CONFIG.setProperty("indent", "no");
173  1 TIDY_FEED_CONFIG.setProperty("quiet", "yes");
174  1 TIDY_FEED_CONFIG.setProperty("trim-empty-elements", "yes");
175   
176    // XML specific configuration
177  1 TIDY_XML_CONFIG = new Properties(TIDY_FEED_CONFIG);
178  1 TIDY_XML_CONFIG.setProperty("input-xml", "yes");
179  1 TIDY_XML_CONFIG.setProperty("output-xml", "yes");
180  1 TIDY_XML_CONFIG.setProperty("add-xml-pi", "no");
181  1 TIDY_XML_CONFIG.setProperty("input-encoding", "UTF8");
182   
183    // HTML specific configuration
184  1 TIDY_HTML_CONFIG = new Properties(TIDY_FEED_CONFIG);
185  1 TIDY_HTML_CONFIG.setProperty("output-xhtml", "yes");
186  1 TIDY_HTML_CONFIG.setProperty("show-body-only", "yes");
187  1 TIDY_HTML_CONFIG.setProperty("drop-empty-paras", "yes");
188  1 TIDY_HTML_CONFIG.setProperty("enclose-text", "yes");
189  1 TIDY_HTML_CONFIG.setProperty("logical-emphasis", "yes");
190  1 TIDY_HTML_CONFIG.setProperty("input-encoding", "UTF8");
191   
192    // default parameters for all instances of this class
193  1 DEFAULT_PARAMS = new HashMap<String, Object>();
194  1 DEFAULT_PARAMS.put(CONTENT_TYPE, "text/html");
195  1 DEFAULT_PARAMS.put(CONTENT_LENGTH, -1); // no limit by default
196    }
197   
198    /**
199    * Strategy instance parameters. Each concrete strategy can define its own (paramName, paramValue) pairs. These
200    * parameters are overwritten by those used when calling
201    * {@link SyndEntrySource#source(SyndEntry, Object, Map, XWikiContext)} method
202    */
203    private Map<String, Object> params;
204   
 
205  8 toggle public SyndEntryDocumentSource()
206    {
207  8 this(new HashMap<String, Object>());
208    }
209   
210    /**
211    * Creates a new instance. The given parameters overwrite {@link #DEFAULT_PARAMS}.
212    *
213    * @param params parameters only for this instance
214    */
 
215  8 toggle public SyndEntryDocumentSource(Map<String, Object> params)
216    {
217  8 setParams(params);
218    }
219   
220    /**
221    * @return instance parameters
222    */
 
223  11 toggle public Map<String, Object> getParams()
224    {
225  11 return this.params;
226    }
227   
228    /**
229    * Sets instance parameters. Instance parameters overwrite {@link #DEFAULT_PARAMS}
230    *
231    * @param params instance parameters
232    */
 
233  10 toggle public void setParams(Map<String, Object> params)
234    {
235  10 this.params = joinParams(params, getDefaultParams());
236    }
237   
238    /**
239    * Strategy class parameters
240    */
 
241  10 toggle protected Map<String, Object> getDefaultParams()
242    {
243  10 return DEFAULT_PARAMS;
244    }
245   
 
246  12 toggle @Override
247    public void source(SyndEntry entry, Object obj, Map<String, Object> params, XWikiContext context)
248    throws XWikiException
249    {
250    // cast source
251  12 Document doc = castDocument(obj, context);
252   
253    // test access rights
254  12 if (!doc.hasAccessLevel("view")) {
255  1 throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED,
256    "Access denied in view mode!");
257    }
258   
259    // prepare parameters (overwrite instance parameters)
260  11 Map<String, Object> trueParams = joinParams(params, getParams());
261   
262  11 sourceDocument(entry, doc, trueParams, context);
263    }
264   
265    /**
266    * Overwrites the current values of the given feed entry with new ones computed from the specified source document.
267    *
268    * @param entry the feed entry whose fields are going to be overwritten
269    * @param doc the source for the new values to be set on the fields of the feed entry
270    * @param params parameters to adjust the computation. Each concrete strategy may define its own (key, value) pairs
271    * @param context the XWiki context
272    * @throws XWikiException
273    */
 
274  11 toggle public void sourceDocument(SyndEntry entry, Document doc, Map<String, Object> params, XWikiContext context)
275    throws XWikiException
276    {
277  11 entry.setUri(getURI(doc, params, context));
278  11 entry.setLink(getLink(doc, params, context));
279  11 entry.setTitle(getTitle(doc, params, context));
280  11 entry.setDescription(getDescription(doc, params, context));
281  11 entry.setCategories(getCategories(doc, params, context));
282  11 entry.setPublishedDate(getPublishedDate(doc, params, context));
283  11 entry.setUpdatedDate(getUpdateDate(doc, params, context));
284  11 entry.setAuthor(getAuthor(doc, params, context));
285  11 entry.setContributors(getContributors(doc, params, context));
286    }
287   
 
288  22 toggle protected String getDefaultURI(Document doc, Map<String, Object> params, XWikiContext context)
289    throws XWikiException
290    {
291  22 return doc.getExternalURL("view", "language=" + doc.getRealLanguage());
292    }
293   
 
294  11 toggle protected String getURI(Document doc, Map<String, Object> params, XWikiContext context) throws XWikiException
295    {
296  11 String mapping = (String) params.get(FIELD_URI);
297  11 if (mapping == null) {
298  11 return getDefaultURI(doc, params, context);
299  0 } else if (isVelocityCode(mapping)) {
300  0 return parseString(mapping, doc, context);
301    } else {
302  0 return getStringValue(mapping, doc, context);
303    }
304    }
305   
 
306  11 toggle protected String getDefaultLink(Document doc, Map<String, Object> params, XWikiContext context)
307    throws XWikiException
308    {
309  11 return getDefaultURI(doc, params, context);
310    }
311   
 
312  11 toggle protected String getLink(Document doc, Map<String, Object> params, XWikiContext context) throws XWikiException
313    {
314  11 String mapping = (String) params.get(FIELD_LINK);
315  11 if (mapping == null) {
316  11 return getDefaultLink(doc, params, context);
317  0 } else if (isVelocityCode(mapping)) {
318  0 return parseString(mapping, doc, context);
319    } else {
320  0 return getStringValue(mapping, doc, context);
321    }
322    }
323   
 
324  11 toggle protected String getDefaultTitle(Document doc, Map<String, Object> params, XWikiContext context)
325    {
326  11 return doc.getDisplayTitle();
327    }
328   
 
329  11 toggle protected String getTitle(Document doc, Map<String, Object> params, XWikiContext context) throws XWikiException
330    {
331  11 String mapping = (String) params.get(FIELD_TITLE);
332  11 if (mapping == null) {
333  11 return getDefaultTitle(doc, params, context);
334  0 } else if (isVelocityCode(mapping)) {
335  0 return parseString(mapping, doc, context);
336    } else {
337  0 return getStringValue(mapping, doc, context);
338    }
339    }
340   
 
341  8 toggle protected String getDefaultDescription(Document doc, Map<String, Object> params, XWikiContext context)
342    {
343  8 XWiki xwiki = context.getWiki();
344  8 String author = xwiki.getUserName(doc.getAuthor(), null, false, context);
345    // the description format should be taken from a resource bundle, and thus localized
346  8 String descFormat = "Version %1$s edited by %2$s on %3$s";
347  8 return String.format(descFormat, new Object[] {doc.getVersion(), author, doc.getDate()});
348    }
349   
 
350  11 toggle protected SyndContent getDescription(Document doc, Map<String, Object> params, XWikiContext context)
351    throws XWikiException
352    {
353  11 String description;
354  11 String mapping = (String) params.get(FIELD_DESCRIPTION);
355  11 if (mapping == null) {
356  8 description = getDefaultDescription(doc, params, context);
357  3 } else if (isVelocityCode(mapping)) {
358  0 description = parseString(mapping, doc, context);
359    } else {
360  3 description = doc.getRenderedContent(getStringValue(mapping, doc, context), doc.getSyntaxId());
361    }
362  11 String contentType = (String) params.get(CONTENT_TYPE);
363  11 int contentLength = ((Number) params.get(CONTENT_LENGTH)).intValue();
364  11 if (contentLength >= 0) {
365  3 if ("text/plain".equals(contentType)) {
366  1 description = getPlainPreview(description, contentLength);
367  2 } else if ("text/html".equals(contentType)) {
368  1 description = getHTMLPreview(description, contentLength);
369  1 } else if ("text/xml".equals(contentType)) {
370  1 description = getXMLPreview(description, contentLength);
371    }
372    }
373  11 return getSyndContent(contentType, description);
374    }
375   
 
376  11 toggle protected List<SyndCategory> getDefaultCategories(Document doc, Map<String, Object> params, XWikiContext context)
377    {
378  11 return Collections.emptyList();
379    }
380   
 
381  11 toggle protected List<SyndCategory> getCategories(Document doc, Map<String, Object> params, XWikiContext context)
382    throws XWikiException
383    {
384  11 String mapping = (String) params.get(FIELD_CATEGORIES);
385  11 if (mapping == null) {
386  11 return getDefaultCategories(doc, params, context);
387    }
388   
389  0 List<Object> categories;
390  0 if (isVelocityCode(mapping)) {
391  0 categories = parseList(mapping, doc, context);
392    } else {
393  0 categories = getListValue(mapping, doc, context);
394    }
395   
396  0 List<SyndCategory> result = new ArrayList<SyndCategory>();
397  0 for (Object category : categories) {
398  0 if (category instanceof SyndCategory) {
399  0 result.add((SyndCategory) category);
400  0 } else if (category != null) {
401  0 SyndCategory scat = new SyndCategoryImpl();
402  0 scat.setName(category.toString());
403  0 result.add(scat);
404    }
405    }
406  0 return result;
407    }
408   
 
409  11 toggle protected Date getDefaultPublishedDate(Document doc, Map<String, Object> params, XWikiContext context)
410    {
411  11 return doc.getDate();
412    }
413   
 
414  11 toggle protected Date getPublishedDate(Document doc, Map<String, Object> params, XWikiContext context)
415    throws XWikiException
416    {
417  11 String mapping = (String) params.get(FIELD_PUBLISHED_DATE);
418  11 if (mapping == null) {
419  11 return getDefaultPublishedDate(doc, params, context);
420  0 } else if (isVelocityCode(mapping)) {
421  0 return parseDate(mapping, doc, context);
422    } else {
423  0 return getDateValue(mapping, doc, context);
424    }
425    }
426   
 
427  11 toggle protected Date getDefaultUpdateDate(Document doc, Map<String, Object> params, XWikiContext context)
428    {
429  11 return doc.getDate();
430    }
431   
 
432  11 toggle protected Date getUpdateDate(Document doc, Map<String, Object> params, XWikiContext context) throws XWikiException
433    {
434  11 String mapping = (String) params.get(FIELD_UPDATED_DATE);
435  11 if (mapping == null) {
436  11 return getDefaultUpdateDate(doc, params, context);
437  0 } else if (isVelocityCode(mapping)) {
438  0 return parseDate(mapping, doc, context);
439    } else {
440  0 return getDateValue(mapping, doc, context);
441    }
442    }
443   
 
444  11 toggle protected String getDefaultAuthor(Document doc, Map<String, Object> params, XWikiContext context)
445    {
446  11 return context.getWiki().getUserName(doc.getCreator(), null, false, context);
447    }
448   
 
449  11 toggle protected String getAuthor(Document doc, Map<String, Object> params, XWikiContext context) throws XWikiException
450    {
451  11 String mapping = (String) params.get(FIELD_AUTHOR);
452  11 if (mapping == null) {
453  11 return getDefaultAuthor(doc, params, context);
454  0 } else if (isVelocityCode(mapping)) {
455  0 return parseString(mapping, doc, context);
456    } else {
457  0 return getStringValue(mapping, doc, context);
458    }
459    }
460   
 
461  11 toggle protected List<String> getDefaultContributors(Document doc, Map<String, Object> params, XWikiContext context)
462    {
463  11 XWiki xwiki = context.getWiki();
464  11 List<String> contributors = new ArrayList<String>();
465  11 contributors.add(xwiki.getUserName(doc.getAuthor(), null, false, context));
466  11 return contributors;
467    }
468   
 
469  11 toggle protected List<String> getContributors(Document doc, Map<String, Object> params, XWikiContext context)
470    throws XWikiException
471    {
472  11 String mapping = (String) params.get(FIELD_CONTRIBUTORS);
473  11 if (mapping == null) {
474  11 return getDefaultContributors(doc, params, context);
475    }
476   
477  0 List<Object> rawContributors;
478  0 if (isVelocityCode(mapping)) {
479  0 rawContributors = parseList(mapping, doc, context);
480    } else {
481  0 rawContributors = getListValue(mapping, doc, context);
482    }
483   
484  0 List<String> contributors = new ArrayList<String>();
485  0 for (Object rawContributor : rawContributors) {
486  0 if (rawContributor instanceof String) {
487  0 contributors.add((String) rawContributor);
488    } else {
489  0 contributors.add(rawContributor.toString());
490    }
491    }
492   
493  0 return contributors;
494    }
495   
496    /**
497    * Distinguishes between mapping to a property and mapping to a velocity code.
498    *
499    * @param mapping
500    * @return true if the given string is a mapping to a velocity code
501    */
 
502  3 toggle protected boolean isVelocityCode(String mapping)
503    {
504  3 return mapping.charAt(0) == '{' && mapping.charAt(mapping.length() - 1) == '}';
505    }
506   
 
507  0 toggle protected String parseString(String mapping, Document doc, XWikiContext context) throws XWikiException
508    {
509  0 if (isVelocityCode(mapping)) {
510  0 return doc.getRenderedContent(mapping.substring(1, mapping.length() - 1), doc.getSyntax().toIdString());
511    } else {
512  0 return mapping;
513    }
514    }
515   
516    /**
517    * Converts the given velocity string to a {@link Date} instance. The velocity code must be evaluated to a long
518    * representing a time stamp.
519    */
 
520  0 toggle protected Date parseDate(String mapping, Document doc, XWikiContext context) throws NumberFormatException,
521    XWikiException
522    {
523  0 if (isVelocityCode(mapping)) {
524  0 return new Date(Long.parseLong(parseString(mapping, doc, context).trim()));
525    } else {
526  0 return null;
527    }
528    }
529   
530    /**
531    * Converts the given velocity code to a {@link List} instance. The velocity code must be evaluated to a string with
532    * the following format: "[item1,item2,...,itemN]".
533    */
 
534  0 toggle protected List<Object> parseList(String mapping, Document doc, XWikiContext context) throws XWikiException
535    {
536  0 if (!isVelocityCode(mapping)) {
537  0 return null;
538    }
539  0 String strRep = parseString(mapping, doc, context).trim();
540  0 if (strRep.charAt(0) != '[' || strRep.charAt(strRep.length() - 1) != ']') {
541  0 return null;
542    }
543  0 String[] array = strRep.substring(1, strRep.length() - 1).split(",");
544  0 if (array.length > 0) {
545  0 List<Object> list = new ArrayList<Object>();
546  0 for (int i = 0; i < array.length; i++) {
547  0 list.add(array[i]);
548    }
549  0 return list;
550    } else {
551  0 return Collections.emptyList();
552    }
553    }
554   
555    /**
556    * Retrieves the value of a string property.
557    */
 
558  3 toggle protected String getStringValue(String mapping, Document doc, XWikiContext context) throws XWikiException
559    {
560  3 PropertySelector ps = new PropertySelector(mapping);
561  3 if (ps.getClassName() == null) {
562  0 return doc.display(ps.getPropertyName());
563    } else {
564  3 XWikiDocument xdoc = context.getWiki().getDocument(doc.getFullName(), context);
565  3 return xdoc.getObject(ps.getClassName(), ps.getObjectIndex()).getStringValue(ps.getPropertyName());
566    }
567    }
568   
569    /**
570    * Retrieves the value of a date property.
571    */
 
572  0 toggle protected Date getDateValue(String mapping, Document doc, XWikiContext context) throws XWikiException
573    {
574  0 XWikiDocument xdoc = context.getWiki().getDocument(doc.getFullName(), context);
575  0 PropertySelector ps = new PropertySelector(mapping);
576  0 if (ps.getClassName() == null) {
577  0 return xdoc.getFirstObject(ps.getPropertyName(), context).getDateValue(ps.getPropertyName());
578    } else {
579  0 return xdoc.getObject(ps.getClassName(), ps.getObjectIndex()).getDateValue(ps.getPropertyName());
580    }
581    }
582   
583    /**
584    * Retrieves the value of a list property.
585    */
 
586  0 toggle protected List<Object> getListValue(String mapping, Document doc, XWikiContext context) throws XWikiException
587    {
588  0 XWikiDocument xdoc = context.getWiki().getDocument(doc.getFullName(), context);
589  0 PropertySelector ps = new PropertySelector(mapping);
590  0 if (ps.getClassName() == null) {
591  0 return xdoc.getFirstObject(ps.getPropertyName(), context).getListValue(ps.getPropertyName());
592    } else {
593  0 return xdoc.getObject(ps.getClassName(), ps.getObjectIndex()).getListValue(ps.getPropertyName());
594    }
595    }
596   
597    /**
598    * @return base + (extra - base)
599    */
 
600  21 toggle protected Map<String, Object> joinParams(Map<String, Object> base, Map<String, Object> extra)
601    {
602  21 Map<String, Object> params = new HashMap<String, Object>();
603  21 params.putAll(base);
604   
605  21 for (Map.Entry<String, Object> entry : extra.entrySet()) {
606  43 if (params.get(entry.getKey()) == null) {
607  35 params.put(entry.getKey(), entry.getValue());
608    }
609    }
610  21 return params;
611    }
612   
613    /**
614    * Cleans up the given XML fragment using the specified configuration.
615    *
616    * @param xmlFragment the XML fragment to be cleaned up
617    * @param config the configuration properties to use
618    * @return the DOM tree associated with the cleaned up XML fragment
619    */
 
620  8 toggle public static org.w3c.dom.Document tidy(String xmlFragment, Properties config)
621    {
622  8 Tidy tidy = new Tidy();
623  8 tidy.setConfigurationFromProps(config);
624    // We capture the logs and redirect them to the XWiki logging subsystem. Since we do this we don't want
625    // JTidy warnings and errors to be sent to stderr/stdout
626  8 tidy.setMessageListener(TIDY_LOGGER);
627  8 tidy.setErrout(new PrintWriter(new NullWriter()));
628    // Even if we add a message listener we still have to redirect the output. Otherwise all the messages will be
629    // written to the standard output (besides being logged by TIDY_LOGGER).
630  8 ByteArrayOutputStream out = new ByteArrayOutputStream();
631  8 ByteArrayInputStream in = new ByteArrayInputStream(xmlFragment.getBytes(Charset.forName(config.getProperty("input-encoding"))));
632  8 return tidy.parseDOM(in, out);
633    }
634   
635    /**
636    * Computes the sum of lengths of all the text nodes within the given DOM sub-tree
637    *
638    * @param node the root of the DOM sub-tree containing the text
639    * @return the sum of lengths of text nodes within the given DOM sub-tree
640    */
 
641  47 toggle public static int innerTextLength(Node node)
642    {
643  47 switch (node.getNodeType()) {
644  10 case Node.TEXT_NODE:
645  10 return node.getNodeValue().length();
646  33 case Node.ELEMENT_NODE:
647  33 int length = 0;
648  33 Node child = node.getFirstChild();
649  72 while (child != null) {
650  39 length += innerTextLength(child);
651  39 child = child.getNextSibling();
652    }
653  33 return length;
654  4 case Node.DOCUMENT_NODE:
655  4 return innerTextLength(((org.w3c.dom.Document) node).getDocumentElement());
656  0 default:
657  0 return 0;
658    }
659    }
660   
661    /**
662    * Extracts the first characters of the given XML fragment, up to the given length limit, adding only characters in
663    * XML text nodes. The XML fragment is cleaned up before extracting the prefix to be sure that the result is
664    * well-formed.
665    *
666    * @param xmlFragment the full XML text
667    * @param previewLength the maximum number of characters allowed in the preview, considering only the XML text nodes
668    * @return a prefix of the given XML fragment summing at most <code>previewLength</code> characters in its text
669    * nodes
670    */
 
671  2 toggle public static String getXMLPreview(String xmlFragment, int previewLength)
672    {
673  2 try {
674  2 return XMLUtils.extractXML(tidy(xmlFragment, TIDY_XML_CONFIG), 0, previewLength);
675    } catch (RuntimeException e) {
676  0 return getPlainPreview(xmlFragment, previewLength);
677    }
678    }
679   
680    /**
681    * Extracts the first characters of the given HTML fragment, up to the given length limit, adding only characters in
682    * HTML text nodes. The HTML fragment is cleaned up before extracting the prefix to be sure the result is
683    * well-formed.
684    *
685    * @param htmlFragment the full HTML text
686    * @param previewLength the maximum number of characters allowed in the preview, considering only the HTML text
687    * nodes
688    * @return a prefix of the given HTML fragment summing at most <code>previewLength</code> characters in its text
689    * nodes
690    */
 
691  2 toggle public static String getHTMLPreview(String htmlFragment, int previewLength)
692    {
693  2 try {
694  2 org.w3c.dom.Document html = tidy(htmlFragment, TIDY_HTML_CONFIG);
695  2 Node body = html.getElementsByTagName("body").item(0);
696  2 return XMLUtils.extractXML(body.getFirstChild(), 0, previewLength);
697    } catch (RuntimeException e) {
698  0 return getPlainPreview(htmlFragment, previewLength);
699    }
700    }
701   
702    /**
703    * Extracts the first characters of the given text, up to the last space within the given length limit.
704    *
705    * @param plainText the full text
706    * @param previewLength the maximum number of characters allowed in the preview
707    * @return a prefix of the <code>plainText</code> having at most <code>previewLength</code> characters
708    */
 
709  2 toggle public static String getPlainPreview(String plainText, int previewLength)
710    {
711  2 if (plainText.length() <= previewLength) {
712  0 return plainText;
713    }
714    // We remove the leading and trailing spaces from the given text to avoid interfering
715    // with the last space within the length limit
716  2 plainText = plainText.trim();
717  2 if (plainText.length() <= previewLength) {
718  0 return plainText;
719    }
720  2 int spaceIndex = plainText.lastIndexOf(" ", previewLength);
721  2 if (spaceIndex < 0) {
722  0 spaceIndex = previewLength;
723    }
724  2 plainText = plainText.substring(0, spaceIndex);
725  2 return plainText;
726    }
727   
728    /**
729    * Creates a new {@link SyndContent} instance for the given content type and value.
730    *
731    * @param type content type
732    * @param value the content
733    * @return a new {@link SyndContent} instance
734    */
 
735  11 toggle protected SyndContent getSyndContent(String type, String value)
736    {
737  11 SyndContent content = new SyndContentImpl();
738  11 content.setType(type);
739  11 content.setValue(value);
740  11 return content;
741    }
742   
743    /**
744    * Casts the given object to a {@link Document} instance. The given object must be either a {@link Document}
745    * instance already, a {@link XWikiDocument} instance or the full name of the document.
746    *
747    * @param obj object to be casted
748    * @param context the XWiki context
749    * @return the document associated with the given object
750    * @throws XWikiException if the given object is neither a {@link Document} instance, a {@link XWikiDocument}
751    * instance nor the full name of the document
752    */
 
753  12 toggle protected Document castDocument(Object obj, XWikiContext context) throws XWikiException
754    {
755  12 if (obj instanceof Document) {
756  1 return (Document) obj;
757  11 } else if (obj instanceof XWikiDocument) {
758  10 return ((XWikiDocument) obj).newDocument(context);
759  1 } else if (obj instanceof String) {
760  1 return context.getWiki().getDocument((String) obj, context).newDocument(context);
761    } else {
762  0 throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS, XWikiException.ERROR_XWIKI_DOES_NOT_EXIST, "");
763    }
764    }
765    }