/******************************************************************************* * Copyright (c) 2010, 2012 David Green and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * David Green - initial API and implementation * Jeremie Bresson - Bug 379783 *******************************************************************************/ package org.eclipse.mylyn.wikitext.mediawiki.internal; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.mylyn.wikitext.mediawiki.Template; import org.eclipse.mylyn.wikitext.mediawiki.TemplateResolver; public class TemplateProcessor { private static final Pattern templatePattern = Pattern .compile("(?:^|(?<!\\{))(\\{\\{(#?[a-zA-Z0-9_ :/()\\.\\-]+)\\s*(\\|[^\\}]*)?\\}\\})"); //$NON-NLS-1$ private static final Pattern templateParameterPattern = Pattern .compile("\\{\\{\\{([a-zA-Z0-9]+)(?:\\|([^\\}]*))?\\}\\}\\}"); //$NON-NLS-1$ private static final Pattern parameterSpec = Pattern.compile("\\|\\s*([^\\|=]+)(?:\\s*=\\s*(([^|]*)))?"); //$NON-NLS-1$ private static final Pattern includeOnlyPattern = Pattern.compile(".*?<includeonly>(.*?)</includeonly>.*", //$NON-NLS-1$ Pattern.DOTALL); private static final Pattern noIncludePattern = Pattern.compile("<noinclude>(.*?)</noinclude>", Pattern.DOTALL); //$NON-NLS-1$ private final AbstractMediaWikiLanguage mediaWikiLanguage; private final Map<String, Template> templateByName = new HashMap<String, Template>(); private final List<Pattern> excludePatterns = new ArrayList<Pattern>(); public TemplateProcessor(AbstractMediaWikiLanguage abstractMediaWikiLanguage) { this.mediaWikiLanguage = abstractMediaWikiLanguage; for (Template template : mediaWikiLanguage.getTemplates()) { templateByName.put(template.getName(), normalize(template)); } String templateExcludes = abstractMediaWikiLanguage.getTemplateExcludes(); if (templateExcludes != null) { String[] split = templateExcludes.split("\\s*,\\s*"); //$NON-NLS-1$ for (String exclude : split) { String pattern = exclude.replaceAll("([^a-zA-Z:\\*])", "\\\\$1").replaceAll("\\*", ".*?"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ excludePatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); } } } public String processTemplates(String markupContent) { return processTemplates(markupContent, Collections.<String> emptySet()); } private String processTemplates(String markupContent, Set<String> usedTemplates) { StringBuilder processedMarkup = new StringBuilder(); int lastIndex = 0; Matcher matcher = templatePattern.matcher(markupContent); while (matcher.find()) { int start = matcher.start(); if (lastIndex < start) { processedMarkup.append(markupContent.substring(lastIndex, start)); } String templateName = matcher.group(2); Template template = resolveTemplate(templateName); if (template != null) { String replacementText; if (usedTemplates.contains(templateName)) { StringBuilder sb = new StringBuilder(); sb.append("<span class=\"error\">"); //$NON-NLS-1$ sb.append(MessageFormat.format(Messages.getString("TemplateProcessor_loopDetected"), //$NON-NLS-1$ template.getName())); sb.append("</span>"); //$NON-NLS-1$ replacementText = sb.toString(); } else { String parameters = matcher.group(3); replacementText = processTemplate(template, parameters); //The replacementText might contain other templates. Add the current template to the set of used template and call recursively this function again: Set<String> templates = new HashSet<String>(usedTemplates); templates.add(templateName); replacementText = processTemplates(replacementText, templates); } replacementText = processTemplates(replacementText); processedMarkup.append(replacementText); } lastIndex = matcher.end(); } if (lastIndex == 0) { return markupContent; } if (lastIndex < markupContent.length()) { processedMarkup.append(markupContent.substring(lastIndex)); } return processedMarkup.toString(); } private String processTemplate(Template template, String parametersText) { if (template.getTemplateMarkup() == null) { return ""; //$NON-NLS-1$ } String macro = template.getTemplateContent(); List<Parameter> parameters = processParameters(parametersText); StringBuilder processedMarkup = new StringBuilder(); int lastIndex = 0; Matcher matcher = templateParameterPattern.matcher(macro); while (matcher.find()) { int start = matcher.start(); if (lastIndex < start) { processedMarkup.append(macro.substring(lastIndex, start)); } String parameterName = matcher.group(1); String parameterValue = matcher.group(2); try { int parameterIndex = Integer.parseInt(parameterName); if (parameterIndex <= parameters.size() && parameterIndex > 0) { parameterValue = parameters.get(parameterIndex - 1).value; } } catch (NumberFormatException e) { for (Parameter param : parameters) { if (parameterName.equalsIgnoreCase(param.name)) { parameterValue = param.value; break; } } } if (parameterValue != null) { processedMarkup.append(parameterValue); } lastIndex = matcher.end(); } if (lastIndex == 0) { return macro; } if (lastIndex < macro.length()) { processedMarkup.append(macro.substring(lastIndex)); } return processedMarkup.toString(); } private List<Parameter> processParameters(String parametersText) { List<Parameter> parameters = new ArrayList<TemplateProcessor.Parameter>(); if (parametersText != null && parametersText.length() > 0) { Matcher matcher = parameterSpec.matcher(parametersText); while (matcher.find()) { String nameOrValue = matcher.group(1); String value = matcher.group(2); Parameter parameter = new Parameter(); if (value != null) { parameter.name = nameOrValue; parameter.value = value; } else { parameter.value = nameOrValue; } parameters.add(parameter); } } return parameters; } private Template resolveTemplate(String templateName) { if (!excludePatterns.isEmpty()) { for (Pattern p : excludePatterns) { if (p.matcher(templateName).matches()) { return null; } } } Template template = templateByName.get(templateName); if (template == null) { for (TemplateResolver resolver : mediaWikiLanguage.getTemplateProviders()) { template = resolver.resolveTemplate(templateName); if (template != null) { template = normalize(template); break; } } if (template == null) { template = new Template(); template.setName(templateName); template.setTemplateMarkup(""); //$NON-NLS-1$ } templateByName.put(template.getName(), template); } return template; } private Template normalize(Template template) { Template normalizedTemplate = new Template(); normalizedTemplate.setName(template.getName()); normalizedTemplate.setTemplateMarkup(normalizeTemplateMarkup(template.getTemplateContent())); return normalizedTemplate; } private String normalizeTemplateMarkup(String templateMarkup) { Matcher matcher = includeOnlyPattern.matcher(templateMarkup); if (matcher.matches()) { return matcher.group(1); } matcher = noIncludePattern.matcher(templateMarkup); return matcher.replaceAll(""); //$NON-NLS-1$ } private static class Parameter { String name; String value; } }