001
014
015 package com.liferay.portlet.wiki.util;
016
017 import com.liferay.portal.kernel.configuration.Filter;
018 import com.liferay.portal.kernel.exception.PortalException;
019 import com.liferay.portal.kernel.exception.SystemException;
020 import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
021 import com.liferay.portal.kernel.log.Log;
022 import com.liferay.portal.kernel.log.LogFactoryUtil;
023 import com.liferay.portal.kernel.portlet.LiferayPortletURL;
024 import com.liferay.portal.kernel.search.Document;
025 import com.liferay.portal.kernel.search.Field;
026 import com.liferay.portal.kernel.search.Hits;
027 import com.liferay.portal.kernel.util.ArrayUtil;
028 import com.liferay.portal.kernel.util.DiffHtmlUtil;
029 import com.liferay.portal.kernel.util.GetterUtil;
030 import com.liferay.portal.kernel.util.HtmlUtil;
031 import com.liferay.portal.kernel.util.HttpUtil;
032 import com.liferay.portal.kernel.util.InstancePool;
033 import com.liferay.portal.kernel.util.ListUtil;
034 import com.liferay.portal.kernel.util.OrderByComparator;
035 import com.liferay.portal.kernel.util.ParamUtil;
036 import com.liferay.portal.kernel.util.PropsKeys;
037 import com.liferay.portal.kernel.util.StringBundler;
038 import com.liferay.portal.kernel.util.StringPool;
039 import com.liferay.portal.kernel.util.StringUtil;
040 import com.liferay.portal.kernel.util.Validator;
041 import com.liferay.portal.security.permission.ActionKeys;
042 import com.liferay.portal.security.permission.PermissionChecker;
043 import com.liferay.portal.theme.ThemeDisplay;
044 import com.liferay.portal.util.PortalUtil;
045 import com.liferay.portal.util.PropsUtil;
046 import com.liferay.portal.util.PropsValues;
047 import com.liferay.portal.util.WebKeys;
048 import com.liferay.portlet.PortletURLUtil;
049 import com.liferay.portlet.documentlibrary.model.DLFileEntry;
050 import com.liferay.portlet.documentlibrary.service.DLFileEntryLocalServiceUtil;
051 import com.liferay.portlet.messageboards.model.MBMessage;
052 import com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil;
053 import com.liferay.portlet.wiki.PageContentException;
054 import com.liferay.portlet.wiki.WikiFormatException;
055 import com.liferay.portlet.wiki.engines.WikiEngine;
056 import com.liferay.portlet.wiki.model.WikiNode;
057 import com.liferay.portlet.wiki.model.WikiPage;
058 import com.liferay.portlet.wiki.model.WikiPageDisplay;
059 import com.liferay.portlet.wiki.service.WikiNodeLocalServiceUtil;
060 import com.liferay.portlet.wiki.service.WikiPageLocalServiceUtil;
061 import com.liferay.portlet.wiki.service.permission.WikiNodePermission;
062 import com.liferay.portlet.wiki.util.comparator.PageCreateDateComparator;
063 import com.liferay.portlet.wiki.util.comparator.PageTitleComparator;
064 import com.liferay.portlet.wiki.util.comparator.PageVersionComparator;
065 import com.liferay.util.ContentUtil;
066
067 import java.io.IOException;
068
069 import java.util.ArrayList;
070 import java.util.Arrays;
071 import java.util.Collections;
072 import java.util.HashSet;
073 import java.util.Iterator;
074 import java.util.List;
075 import java.util.Map;
076 import java.util.Set;
077 import java.util.concurrent.ConcurrentHashMap;
078 import java.util.regex.Matcher;
079 import java.util.regex.Pattern;
080
081 import javax.portlet.PortletPreferences;
082 import javax.portlet.PortletRequest;
083 import javax.portlet.PortletURL;
084 import javax.portlet.RenderRequest;
085 import javax.portlet.RenderResponse;
086
087
091 public class WikiUtil {
092
093 public static String convert(
094 WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
095 String attachmentURLPrefix)
096 throws PageContentException, WikiFormatException {
097
098 return _instance._convert(
099 page, viewPageURL, editPageURL, attachmentURLPrefix);
100 }
101
102 public static String diffHtml(
103 WikiPage sourcePage, WikiPage targetPage, PortletURL viewPageURL,
104 PortletURL editPageURL, String attachmentURLPrefix)
105 throws Exception {
106
107 String sourceContent = StringPool.BLANK;
108 String targetContent = StringPool.BLANK;
109
110 if (sourcePage != null) {
111 sourceContent = convert(
112 sourcePage, viewPageURL, editPageURL, attachmentURLPrefix);
113 }
114
115 if (targetPage != null) {
116 targetContent = convert(
117 targetPage, viewPageURL, editPageURL, attachmentURLPrefix);
118 }
119
120 return DiffHtmlUtil.diff(
121 new UnsyncStringReader(sourceContent),
122 new UnsyncStringReader(targetContent));
123 }
124
125 public static List<WikiPage> filterOrphans(List<WikiPage> pages)
126 throws PortalException {
127
128 List<Map<String, Boolean>> pageTitles =
129 new ArrayList<Map<String, Boolean>>();
130
131 for (WikiPage page : pages) {
132 pageTitles.add(WikiCacheUtil.getOutgoingLinks(page));
133 }
134
135 Set<WikiPage> notOrphans = new HashSet<WikiPage>();
136
137 for (WikiPage page : pages) {
138 for (Map<String, Boolean> pageTitle : pageTitles) {
139 String pageTitleLowerCase = page.getTitle();
140
141 pageTitleLowerCase = StringUtil.toLowerCase(pageTitleLowerCase);
142
143 if (pageTitle.get(pageTitleLowerCase) != null) {
144 notOrphans.add(page);
145
146 break;
147 }
148 }
149 }
150
151 List<WikiPage> orphans = new ArrayList<WikiPage>();
152
153 for (WikiPage page : pages) {
154 if (!notOrphans.contains(page)) {
155 orphans.add(page);
156 }
157 }
158
159 orphans = ListUtil.sort(orphans);
160
161 return orphans;
162 }
163
164 public static String getAttachmentURLPrefix(
165 String mainPath, long plid, long nodeId, String title) {
166
167 StringBundler sb = new StringBundler(8);
168
169 sb.append(mainPath);
170 sb.append("/wiki/get_page_attachment?p_l_id=");
171 sb.append(plid);
172 sb.append("&nodeId=");
173 sb.append(nodeId);
174 sb.append("&title=");
175 sb.append(HttpUtil.encodeURL(title));
176 sb.append("&fileName=");
177
178 return sb.toString();
179 }
180
181 public static String getEditPage(String format) {
182 return _instance._getEditPage(format);
183 }
184
185 public static String getEmailFromAddress(
186 PortletPreferences preferences, long companyId)
187 throws SystemException {
188
189 return PortalUtil.getEmailFromAddress(
190 preferences, companyId, PropsValues.WIKI_EMAIL_FROM_ADDRESS);
191 }
192
193 public static String getEmailFromName(
194 PortletPreferences preferences, long companyId)
195 throws SystemException {
196
197 return PortalUtil.getEmailFromName(
198 preferences, companyId, PropsValues.WIKI_EMAIL_FROM_NAME);
199 }
200
201 public static String getEmailPageAddedBody(PortletPreferences preferences) {
202 String emailPageAddedBody = preferences.getValue(
203 "emailPageAddedBody", StringPool.BLANK);
204
205 if (Validator.isNotNull(emailPageAddedBody)) {
206 return emailPageAddedBody;
207 }
208 else {
209 return ContentUtil.get(
210 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_BODY));
211 }
212 }
213
214 public static boolean getEmailPageAddedEnabled(
215 PortletPreferences preferences) {
216
217 String emailPageAddedEnabled = preferences.getValue(
218 "emailPageAddedEnabled", StringPool.BLANK);
219
220 if (Validator.isNotNull(emailPageAddedEnabled)) {
221 return GetterUtil.getBoolean(emailPageAddedEnabled);
222 }
223 else {
224 return GetterUtil.getBoolean(
225 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_ENABLED));
226 }
227 }
228
229 public static String getEmailPageAddedSignature(
230 PortletPreferences preferences) {
231
232 String emailPageAddedSignature = preferences.getValue(
233 "emailPageAddedSignature", StringPool.BLANK);
234
235 if (Validator.isNotNull(emailPageAddedSignature)) {
236 return emailPageAddedSignature;
237 }
238 else {
239 return ContentUtil.get(
240 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_SIGNATURE));
241 }
242 }
243
244 public static String getEmailPageAddedSubject(
245 PortletPreferences preferences) {
246
247 String emailPageAddedSubject = preferences.getValue(
248 "emailPageAddedSubject", StringPool.BLANK);
249
250 if (Validator.isNotNull(emailPageAddedSubject)) {
251 return emailPageAddedSubject;
252 }
253 else {
254 return ContentUtil.get(
255 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_SUBJECT));
256 }
257 }
258
259 public static String getEmailPageUpdatedBody(
260 PortletPreferences preferences) {
261
262 String emailPageUpdatedBody = preferences.getValue(
263 "emailPageUpdatedBody", StringPool.BLANK);
264
265 if (Validator.isNotNull(emailPageUpdatedBody)) {
266 return emailPageUpdatedBody;
267 }
268 else {
269 return ContentUtil.get(
270 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_BODY));
271 }
272 }
273
274 public static boolean getEmailPageUpdatedEnabled(
275 PortletPreferences preferences) {
276
277 String emailPageUpdatedEnabled = preferences.getValue(
278 "emailPageUpdatedEnabled", StringPool.BLANK);
279
280 if (Validator.isNotNull(emailPageUpdatedEnabled)) {
281 return GetterUtil.getBoolean(emailPageUpdatedEnabled);
282 }
283 else {
284 return GetterUtil.getBoolean(
285 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_ENABLED));
286 }
287 }
288
289 public static String getEmailPageUpdatedSignature(
290 PortletPreferences preferences) {
291
292 String emailPageUpdatedSignature = preferences.getValue(
293 "emailPageUpdatedSignature", StringPool.BLANK);
294
295 if (Validator.isNotNull(emailPageUpdatedSignature)) {
296 return emailPageUpdatedSignature;
297 }
298 else {
299 return ContentUtil.get(
300 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_SIGNATURE));
301 }
302 }
303
304 public static String getEmailPageUpdatedSubject(
305 PortletPreferences preferences) {
306
307 String emailPageUpdatedSubject = preferences.getValue(
308 "emailPageUpdatedSubject", StringPool.BLANK);
309
310 if (Validator.isNotNull(emailPageUpdatedSubject)) {
311 return emailPageUpdatedSubject;
312 }
313 else {
314 return ContentUtil.get(
315 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_SUBJECT));
316 }
317 }
318
319 public static List<Object> getEntries(Hits hits) {
320 List<Object> entries = new ArrayList<Object>();
321
322 for (Document document : hits.getDocs()) {
323 String entryClassName = GetterUtil.getString(
324 document.get(Field.ENTRY_CLASS_NAME));
325 long entryClassPK = GetterUtil.getLong(
326 document.get(Field.ENTRY_CLASS_PK));
327
328 try {
329 Object obj = null;
330
331 if (entryClassName.equals(DLFileEntry.class.getName())) {
332 long classPK = GetterUtil.getLong(
333 document.get(Field.CLASS_PK));
334
335 WikiPageLocalServiceUtil.getPage(classPK);
336
337 obj = DLFileEntryLocalServiceUtil.getDLFileEntry(
338 entryClassPK);
339 }
340 else if (entryClassName.equals(MBMessage.class.getName())) {
341 long classPK = GetterUtil.getLong(
342 document.get(Field.CLASS_PK));
343
344 WikiPageLocalServiceUtil.getPage(classPK);
345
346 obj = MBMessageLocalServiceUtil.getMessage(entryClassPK);
347 }
348 else if (entryClassName.equals(WikiPage.class.getName())) {
349 obj = WikiPageLocalServiceUtil.getPage(entryClassPK);
350 }
351
352 entries.add(obj);
353 }
354 catch (Exception e) {
355 if (_log.isWarnEnabled()) {
356 _log.warn(
357 "Wiki search index is stale and contains entry " +
358 "{className=" + entryClassName + ", classPK=" +
359 entryClassPK + "}");
360 }
361 }
362 }
363
364 return entries;
365 }
366
367 public static WikiNode getFirstNode(PortletRequest portletRequest)
368 throws PortalException, SystemException {
369
370 ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
371 WebKeys.THEME_DISPLAY);
372 long groupId = themeDisplay.getScopeGroupId();
373 PermissionChecker permissionChecker =
374 themeDisplay.getPermissionChecker();
375
376 List<WikiNode> nodes = WikiNodeLocalServiceUtil.getNodes(groupId);
377
378 PortletPreferences preferences = portletRequest.getPreferences();
379 String[] visibleNodeNames = StringUtil.split(
380 preferences.getValue("visibleNodes", null));
381 nodes = orderNodes(nodes, visibleNodeNames);
382
383 String[] hiddenNodes = StringUtil.split(
384 preferences.getValue("hiddenNodes", StringPool.BLANK));
385 Arrays.sort(hiddenNodes);
386
387 for (WikiNode node : nodes) {
388 if ((Arrays.binarySearch(hiddenNodes, node.getName()) < 0) &&
389 WikiNodePermission.contains(
390 permissionChecker, node, ActionKeys.VIEW)) {
391
392 return node;
393 }
394 }
395
396 return null;
397 }
398
399 public static String getFormattedContent(
400 RenderRequest renderRequest, RenderResponse renderResponse,
401 WikiPage wikiPage, PortletURL viewPageURL, PortletURL editPageURL,
402 String title, boolean preview)
403 throws Exception {
404
405 ThemeDisplay themeDisplay = (ThemeDisplay)renderRequest.getAttribute(
406 WebKeys.THEME_DISPLAY);
407
408 double version = ParamUtil.getDouble(renderRequest, "version");
409
410 PortletURL curViewPageURL = PortletURLUtil.clone(
411 viewPageURL, renderResponse);
412 PortletURL curEditPageURL = PortletURLUtil.clone(
413 editPageURL, renderResponse);
414
415 StringBundler sb = new StringBundler();
416
417 sb.append(themeDisplay.getPathMain());
418 sb.append("/wiki/get_page_attachment?p_l_id=");
419 sb.append(themeDisplay.getPlid());
420 sb.append("&nodeId=");
421 sb.append(wikiPage.getNodeId());
422 sb.append("&title=");
423 sb.append(HttpUtil.encodeURL(wikiPage.getTitle()));
424 sb.append("&fileName=");
425
426 String attachmentURLPrefix = sb.toString();
427
428 if (!preview && (version == 0)) {
429 WikiPageDisplay pageDisplay = WikiCacheUtil.getDisplay(
430 wikiPage.getNodeId(), title, curViewPageURL, curEditPageURL,
431 attachmentURLPrefix);
432
433 if (pageDisplay != null) {
434 return pageDisplay.getFormattedContent();
435 }
436 }
437
438 return convert(
439 wikiPage, curViewPageURL, curEditPageURL, attachmentURLPrefix);
440 }
441
442 public static String getHelpPage(String format) {
443 return _instance._getHelpPage(format);
444 }
445
446 public static String getHelpURL(String format) {
447 return _instance._getHelpURL(format);
448 }
449
450 public static Map<String, Boolean> getLinks(WikiPage page)
451 throws PageContentException {
452
453 return _instance._getLinks(page);
454 }
455
456 public static List<String> getNodeNames(List<WikiNode> nodes) {
457 List<String> nodeNames = new ArrayList<String>(nodes.size());
458
459 for (WikiNode node : nodes) {
460 nodeNames.add(node.getName());
461 }
462
463 return nodeNames;
464 }
465
466 public static List<WikiNode> getNodes(
467 List<WikiNode> nodes, String[] hiddenNodes,
468 PermissionChecker permissionChecker) {
469
470 nodes = ListUtil.copy(nodes);
471
472 Arrays.sort(hiddenNodes);
473
474 Iterator<WikiNode> itr = nodes.iterator();
475
476 while (itr.hasNext()) {
477 WikiNode node = itr.next();
478
479 if (!(Arrays.binarySearch(hiddenNodes, node.getName()) < 0) ||
480 !WikiNodePermission.contains(
481 permissionChecker, node, ActionKeys.VIEW)) {
482
483 itr.remove();
484 }
485 }
486
487 return nodes;
488 }
489
490 public static OrderByComparator getPageOrderByComparator(
491 String orderByCol, String orderByType) {
492
493 boolean orderByAsc = false;
494
495 if (orderByType.equals("asc")) {
496 orderByAsc = true;
497 }
498
499 OrderByComparator orderByComparator = null;
500
501 if (orderByCol.equals("modifiedDate")) {
502 orderByComparator = new PageCreateDateComparator(orderByAsc);
503 }
504 else if (orderByCol.equals("title")) {
505 orderByComparator = new PageTitleComparator(orderByAsc);
506 }
507 else if (orderByCol.equals("version")) {
508 orderByComparator = new PageVersionComparator(orderByAsc);
509 }
510
511 return orderByComparator;
512 }
513
514 public static List<WikiNode> orderNodes(
515 List<WikiNode> nodes, String[] visibleNodeNames) {
516
517 if (ArrayUtil.isEmpty(visibleNodeNames)) {
518 return nodes;
519 }
520
521 nodes = ListUtil.copy(nodes);
522
523 List<WikiNode> orderedNodes = new ArrayList<WikiNode>(nodes.size());
524
525 for (String visibleNodeName : visibleNodeNames) {
526 for (WikiNode node : nodes) {
527 if (node.getName().equals(visibleNodeName)) {
528 orderedNodes.add(node);
529
530 nodes.remove(node);
531
532 break;
533 }
534 }
535 }
536
537 orderedNodes.addAll(nodes);
538
539 return orderedNodes;
540 }
541
542 public static String processContent(String content) {
543 content = content.replaceAll("</p>", "</p>\n");
544 content = content.replaceAll("</br>", "</br>\n");
545 content = content.replaceAll("</div>", "</div>\n");
546
547 return content;
548 }
549
550 public static boolean validate(long nodeId, String content, String format)
551 throws WikiFormatException {
552
553 return _instance._validate(nodeId, content, format);
554 }
555
556 private String _convert(
557 WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
558 String attachmentURLPrefix)
559 throws PageContentException, WikiFormatException {
560
561 LiferayPortletURL liferayViewPageURL = (LiferayPortletURL)viewPageURL;
562 LiferayPortletURL liferayEditPageURL = (LiferayPortletURL)editPageURL;
563
564 WikiEngine engine = _getEngine(page.getFormat());
565
566 String content = engine.convert(
567 page, viewPageURL, editPageURL, attachmentURLPrefix);
568
569 String editPageURLString = StringPool.BLANK;
570
571 if (editPageURL != null) {
572 liferayEditPageURL.setParameter("title", "__REPLACEMENT__", false);
573
574 editPageURLString = editPageURL.toString();
575
576 editPageURLString = StringUtil.replace(
577 editPageURLString, "__REPLACEMENT__", "$1");
578 }
579
580 Matcher matcher = _editPageURLPattern.matcher(content);
581
582 content = _convertURLs(editPageURLString, matcher);
583
584 String viewPageURLString = StringPool.BLANK;
585
586 if (viewPageURL != null) {
587 liferayViewPageURL.setParameter("title", "__REPLACEMENT__", false);
588
589 viewPageURLString = viewPageURL.toString();
590
591 viewPageURLString = StringUtil.replace(
592 viewPageURLString, "__REPLACEMENT__", "$1");
593 }
594
595 matcher = _viewPageURLPattern.matcher(content);
596
597 content = _convertURLs(viewPageURLString, matcher);
598
599 content = _replaceAttachments(
600 content, page.getTitle(), attachmentURLPrefix);
601
602 return content;
603 }
604
605 private String _convertURLs(String url, Matcher matcher) {
606 StringBuffer sb = new StringBuffer();
607
608 while (matcher.find()) {
609 String replacement = null;
610
611 if (matcher.groupCount() >= 1) {
612 String encodedTitle = HttpUtil.encodeURL(
613 HtmlUtil.unescape(matcher.group(1)));
614
615 replacement = url.replace("$1", encodedTitle);
616 }
617 else {
618 replacement = url;
619 }
620
621 matcher.appendReplacement(sb, replacement);
622 }
623
624 return matcher.appendTail(sb).toString();
625 }
626
627 private String _getEditPage(String format) {
628 return PropsUtil.get(
629 PropsKeys.WIKI_FORMATS_EDIT_PAGE, new Filter(format));
630 }
631
632 private WikiEngine _getEngine(String format) throws WikiFormatException {
633 WikiEngine engine = _engines.get(format);
634
635 if (engine != null) {
636 return engine;
637 }
638
639 synchronized (_engines) {
640 engine = _engines.get(format);
641
642 if (engine != null) {
643 return engine;
644 }
645
646 try {
647 String engineClass = PropsUtil.get(
648 PropsKeys.WIKI_FORMATS_ENGINE, new Filter(format));
649
650 if (engineClass == null) {
651 throw new WikiFormatException(format);
652 }
653
654 if (!InstancePool.contains(engineClass)) {
655 engine = (WikiEngine)InstancePool.get(engineClass);
656
657 engine.setMainConfiguration(
658 _readConfigurationFile(
659 PropsKeys.WIKI_FORMATS_CONFIGURATION_MAIN, format));
660
661 engine.setInterWikiConfiguration(
662 _readConfigurationFile(
663 PropsKeys.WIKI_FORMATS_CONFIGURATION_INTERWIKI,
664 format));
665 }
666 else {
667 engine = (WikiEngine)InstancePool.get(engineClass);
668 }
669
670 _engines.put(format, engine);
671
672 return engine;
673 }
674 catch (Exception e) {
675 throw new WikiFormatException(e);
676 }
677 }
678 }
679
680 private String _getHelpPage(String format) {
681 return PropsUtil.get(
682 PropsKeys.WIKI_FORMATS_HELP_PAGE, new Filter(format));
683 }
684
685 private String _getHelpURL(String format) {
686 return PropsUtil.get(
687 PropsKeys.WIKI_FORMATS_HELP_URL, new Filter(format));
688 }
689
690 private Map<String, Boolean> _getLinks(WikiPage page)
691 throws PageContentException {
692
693 try {
694 return _getEngine(page.getFormat()).getOutgoingLinks(page);
695 }
696 catch (WikiFormatException wfe) {
697 return Collections.emptyMap();
698 }
699 }
700
701 private String _readConfigurationFile(String propertyName, String format)
702 throws IOException {
703
704 ClassLoader classLoader = getClass().getClassLoader();
705
706 String configurationFile = PropsUtil.get(
707 propertyName, new Filter(format));
708
709 if (Validator.isNotNull(configurationFile)) {
710 return HttpUtil.URLtoString(
711 classLoader.getResource(configurationFile));
712 }
713 else {
714 return StringPool.BLANK;
715 }
716 }
717
718 private String _replaceAttachments(
719 String content, String title, String attachmentURLPrefix) {
720
721 content = StringUtil.replace(content, "[$WIKI_PAGE_NAME$]", title);
722
723 content = StringUtil.replace(
724 content, "[$ATTACHMENT_URL_PREFIX$]", attachmentURLPrefix);
725
726 return content;
727 }
728
729 private boolean _validate(long nodeId, String content, String format)
730 throws WikiFormatException {
731
732 return _getEngine(format).validate(nodeId, content);
733 }
734
735 private static Log _log = LogFactoryUtil.getLog(WikiUtil.class);
736
737 private static WikiUtil _instance = new WikiUtil();
738
739 private static Pattern _editPageURLPattern = Pattern.compile(
740 "\\[\\$BEGIN_PAGE_TITLE_EDIT\\$\\](.*?)" +
741 "\\[\\$END_PAGE_TITLE_EDIT\\$\\]");
742 private static Pattern _viewPageURLPattern = Pattern.compile(
743 "\\[\\$BEGIN_PAGE_TITLE\\$\\](.*?)\\[\\$END_PAGE_TITLE\\$\\]");
744
745 private Map<String, WikiEngine> _engines =
746 new ConcurrentHashMap<String, WikiEngine>();
747
748 }