/*
* WPCleaner: A tool to help on Wikipedia maintenance tasks.
* Copyright (C) 2013 Nicolas Vervelle
*
* See README.txt file for licensing information.
*/
package org.wikipediacleaner.api.check.algorithm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JOptionPane;
import org.wikipediacleaner.api.check.AddTextActionProvider;
import org.wikipediacleaner.api.check.CheckErrorResult;
import org.wikipediacleaner.api.check.SimpleAction;
import org.wikipediacleaner.api.constants.WPCConfigurationStringList;
import org.wikipediacleaner.api.data.PageAnalysis;
import org.wikipediacleaner.api.data.PageElementExternalLink;
import org.wikipediacleaner.api.data.PageElementTag;
import org.wikipediacleaner.api.data.PageElementTag.Parameter;
import org.wikipediacleaner.gui.swing.action.ActionExternalViewer;
import org.wikipediacleaner.gui.swing.basic.Utilities;
import org.wikipediacleaner.gui.swing.component.MWPane;
import org.wikipediacleaner.i18n.GT;
import org.wikipediacleaner.utils.StringChecker;
import org.wikipediacleaner.utils.StringCheckerReferenceName;
import org.wikipediacleaner.utils.TextProvider;
import org.wikipediacleaner.utils.TextProviderUrlTitle;
/**
* Algorithm for analyzing error 81 of check wikipedia project.
* Error 81: Reference duplication.
*/
public class CheckErrorAlgorithm081 extends CheckErrorAlgorithmBase {
/**
* String checker for the reference name.
*/
private final StringChecker nameChecker;
/**
* Possible global fixes.
*/
private final static String[] globalFixes = new String[] {
GT._("Fix reference duplication"),
};
public CheckErrorAlgorithm081() {
super("Reference duplication");
nameChecker = new StringCheckerReferenceName();
}
/**
* Group tags by group and value.
*
* @param analysis Page analysis.
* @param refs Tags (out).
* @return True if there are several tags with the same text.
*/
private boolean groupTags(
PageAnalysis analysis,
Map<String, Map<String, List<PageElementTag>>> refs) {
List<PageElementTag> completeRefTags =
analysis.getCompleteTags(PageElementTag.TAG_WIKI_REF);
String contents = analysis.getContents();
boolean result = false;
for (PageElementTag tag : completeRefTags) {
int valueBeginIndex = tag.getValueBeginIndex();
int valueEndIndex = tag.getValueEndIndex();
if ((!tag.isFullTag()) &&
(valueBeginIndex > 0) &&
(valueEndIndex > 0) &&
(valueBeginIndex < valueEndIndex)) {
// Retrieve references with the same group name
String groupName = tag.getGroupOfRef(analysis);
Map<String, List<PageElementTag>> groupRefs = refs.get(groupName);
if (groupRefs == null) {
groupRefs = new HashMap<String, List<PageElementTag>>();
refs.put(groupName, groupRefs);
}
// Retrieve references with the same text
String text = contents.substring(valueBeginIndex, valueEndIndex).trim();
if (text.length() > 0) {
List<PageElementTag> valueRefs = groupRefs.get(text);
if (valueRefs == null) {
valueRefs = new ArrayList<PageElementTag>();
groupRefs.put(text, valueRefs);
}
if (valueRefs.size() > 0) {
result = true;
}
valueRefs.add(tag);
}
}
}
return result;
}
/**
* Get all names used in a list of reference tags.
*
* @param refs List of reference tags.
* @return List of names.
*/
private List<String> getRefNames(List<PageElementTag> refs) {
List<String> possibleNames = new ArrayList<String>();
for (PageElementTag tag : refs) {
Parameter name = tag.getParameter("name");
if ((name != null) && (name.getTrimmedValue() != null)) {
String nameValue = name.getTrimmedValue();
if ((nameValue.length() > 0) && (!possibleNames.contains(nameValue))) {
possibleNames.add(nameValue);
}
}
}
return possibleNames;
}
/**
* Construct a closed reference tag.
*
* @param groupName Name of the group.
* @param tagName Name of the tag.
* @param value Value of the tag.
* @return Reference tag.
*/
private String getClosedRefTag(String groupName, String tagName, String value) {
StringBuilder result = new StringBuilder();
result.append("<ref");
if ((groupName != null) && (groupName.trim().length() > 0)) {
result.append(" group=\"");
result.append(groupName.trim());
result.append("\"");
}
if ((tagName != null) && (tagName.trim().length() > 0)) {
result.append(" name=\"");
result.append(tagName.trim());
result.append("\"");
}
if ((value != null) && (value.trim().length() > 0)) {
result.append(">");
result.append(value.trim());
result.append("</ref>");
} else {
result.append(" />");
}
return result.toString();
}
/**
* Analyze a page to check if errors are present.
*
* @param analysis Page analysis.
* @param errors Errors found in the page.
* @param onlyAutomatic True if analysis could be restricted to errors automatically fixed.
* @return Flag indicating if the error was found.
*/
@Override
public boolean analyze(
PageAnalysis analysis,
Collection<CheckErrorResult> errors, boolean onlyAutomatic) {
if (analysis == null) {
return false;
}
// Group tags by group and value for further analyze
Map<String, Map<String, List<PageElementTag>>> refs =
new HashMap<String, Map<String, List<PageElementTag>>>();
boolean result = groupTags(analysis, refs);
if (result == false) {
return false;
} else if (errors == null) {
return true;
}
// Second pass for managing with several tags having the same group and value
List<PageElementTag> completeReferencesTags =
analysis.getCompleteTags(PageElementTag.TAG_WIKI_REFERENCES);
String contents = analysis.getContents();
for (Entry<String, Map<String, List<PageElementTag>>> entryGroup : refs.entrySet()) {
String groupName = entryGroup.getKey();
for (Entry<String, List<PageElementTag>> entryValue : entryGroup.getValue().entrySet()) {
List<PageElementTag> listTags = entryValue.getValue();
if (listTags.size() > 1) {
// Find main reference tag
PageElementTag mainTag = PageElementTag.getMainRef(
listTags, completeReferencesTags, analysis);
if (mainTag != null) {
// Create an error for each tag, except for the main tag
String selectedName = mainTag.getParameter("name").getTrimmedValue();
for (PageElementTag tag : listTags) {
if (tag == mainTag) {
CheckErrorResult errorResult = createCheckErrorResult(
analysis,
tag.getCompleteBeginIndex(), tag.getCompleteEndIndex(),
CheckErrorResult.ErrorLevel.CORRECT);
errors.add(errorResult);
} else {
Parameter name = tag.getParameter("name");
String nameValue = (name != null) ? name.getTrimmedValue() : null;
if (nameValue != null) {
nameValue = nameValue.trim();
}
CheckErrorResult errorResult = createCheckErrorResult(
analysis,
tag.getCompleteBeginIndex(), tag.getCompleteEndIndex());
errorResult.addReplacement(
getClosedRefTag(groupName, selectedName, null),
selectedName.equals(nameValue) || (name == null));
errors.add(errorResult);
}
}
} else {
for (PageElementTag tag : listTags) {
int valueBeginIndex = tag.getValueBeginIndex();
int valueEndIndex = tag.getValueEndIndex();
// Find if an external link is in the reference tag
List<PageElementExternalLink> externalLinks = analysis.getExternalLinks();
List<PageElementExternalLink> links = new ArrayList<PageElementExternalLink>();
for (PageElementExternalLink externalLink : externalLinks) {
if ((externalLink.getBeginIndex() >= valueBeginIndex) &&
(externalLink.getEndIndex() <= valueEndIndex)) {
links.add(externalLink);
}
}
// Register error
CheckErrorResult errorResult = createCheckErrorResult(
analysis,
tag.getCompleteBeginIndex(), tag.getCompleteEndIndex());
// Add an action for naming the reference tag
// TODO: manage a better action for naming the reference tag and replacing all other tags
TextProvider provider = null;
if (links.size() > 0) {
provider = new TextProviderUrlTitle(links.get(0).getLink());
}
String prefix = contents.substring(tag.getBeginIndex(), tag.getEndIndex() - 1);
String suffix = contents.substring(tag.getEndIndex() - 1, tag.getCompleteEndIndex());
errorResult.addPossibleAction(
GT._("Give a name to the <ref> tag"),
new AddTextActionProvider(
prefix + " name=\"",
"\"" + suffix,
provider,
GT._("What name would you like to use for the <ref> tag ?"),
nameChecker));
// Add actions for external links
for (PageElementExternalLink link : links) {
errorResult.addPossibleAction(new SimpleAction(
GT._("External Viewer"),
new ActionExternalViewer(link.getLink())));
}
errors.add(errorResult);
}
}
}
}
}
return true;
}
/**
* @return List of possible global fixes.
*/
@Override
public String[] getGlobalFixes() {
return globalFixes;
}
/**
* Fix all the errors in the page.
*
* @param fixName Fix name (extracted from getGlobalFixes()).
* @param analysis Page analysis.
* @param textPane Text pane.
* @return Page contents after fix.
*/
@Override
public String fix(String fixName, PageAnalysis analysis, MWPane textPane) {
// Initialize
StringBuilder tmpContents = new StringBuilder();
int currentIndex = 0;
// Group tags by group and value for further analyze
Map<String, Map<String, List<PageElementTag>>> refsByGroupAndValue =
new HashMap<String, Map<String, List<PageElementTag>>>();
groupTags(analysis, refsByGroupAndValue);
// Memorize tag names by group and value
Map<String, Map<String, List<String>>> refNamesByGroupAndValue =
new HashMap<String, Map<String, List<String>>>();
for (Entry<String, Map<String, List<PageElementTag>>> refsByValueForGroup : refsByGroupAndValue.entrySet()) {
String groupName = refsByValueForGroup.getKey();
Map<String, List<PageElementTag>> groupRefsByValue = refsByValueForGroup.getValue();
Map<String, List<String>> groupRefNamesByValue = new HashMap<String, List<String>>();
refNamesByGroupAndValue.put(groupName, groupRefNamesByValue);
for (Entry<String, List<PageElementTag>> refsForGroupAndValue : groupRefsByValue.entrySet()) {
String value = refsForGroupAndValue.getKey();
List<PageElementTag> refTags = refsForGroupAndValue.getValue();
List<String> possibleNames = getRefNames(refTags);
groupRefNamesByValue.put(value, possibleNames);
}
}
// Check all reference tags
List<PageElementTag> completeReferencesTags =
analysis.getCompleteTags(PageElementTag.TAG_WIKI_REFERENCES);
List<PageElementTag> completeRefTags =
analysis.getCompleteTags(PageElementTag.TAG_WIKI_REF);
Object highlight = null;
String contents = analysis.getContents();
for (PageElementTag refTag : completeRefTags) {
// Retrieve basic information
PageElementTag.Parameter groupParameter = refTag.getParameter("group");
String groupName = (groupParameter != null) ? groupParameter.getValue() : null;
Map<String, List<PageElementTag>> groupRefs = refsByGroupAndValue.get(groupName);
String valueRef = contents.substring(refTag.getValueBeginIndex(), refTag.getValueEndIndex());
List<PageElementTag> valueRefs = groupRefs.get(valueRef);
// Check if there is more than one tag with the same value
if ((valueRefs != null) && (valueRefs.size() > 1)) {
// Display selection
highlight = addHighlight(
textPane, refTag.getCompleteBeginIndex(), refTag.getCompleteEndIndex());
textPane.select(refTag.getCompleteBeginIndex(), refTag.getCompleteEndIndex());
// Check if a name already exists
String replacement = null;
List<String> possibleNames = refNamesByGroupAndValue.get(groupName).get(valueRef);
if ((possibleNames != null) && (possibleNames.size() > 0)) {
String selectedName = possibleNames.get(0);
PageElementTag mainRef = PageElementTag.getMainRef(
valueRefs, completeReferencesTags, analysis);
if (mainRef != refTag) {
String tmp = getClosedRefTag(groupName, selectedName, null);
String message =
GT._("A <ref> tag shares the same content, and is named \"{0}\".", selectedName) + "\n" +
GT._("Do you want to replace this <ref> tag by \"{0}\" ?", tmp);
int answer = Utilities.displayYesNoCancelWarning(textPane.getParent(), message);
if (answer == JOptionPane.YES_OPTION) {
replacement = tmp;
} else if (answer == JOptionPane.CANCEL_OPTION) {
break;
}
}
} else {
String message =
GT._("Several <ref> tags share the same content, but none has been given a name.") +"\n" +
GT._("What name do you want to use for this <ref> tag ?");
String newText = Utilities.askForValue(textPane.getParent(), message, (String) null, nameChecker);
if (newText != null) {
replacement = getClosedRefTag(groupName, newText, valueRef);
possibleNames = Collections.singletonList(newText);
refNamesByGroupAndValue.get(groupName).put(valueRef, possibleNames);
}
}
// Do the replacement
if (replacement != null) {
if (currentIndex < refTag.getBeginIndex()) {
tmpContents.append(contents.substring(currentIndex, refTag.getCompleteBeginIndex()));
}
tmpContents.append(replacement);
currentIndex = refTag.getCompleteEndIndex();
}
removeHighlight(textPane, highlight);
highlight = null;
}
}
removeHighlight(textPane, highlight);
highlight = null;
// Return result
if (currentIndex == 0) {
return contents;
}
if (currentIndex < contents.length()) {
tmpContents.append(contents.substring(currentIndex));
}
return tmpContents.toString();
}
/**
* Automatic fixing of all the errors in the page.
*
* @param analysis Page analysis.
* @return Page contents after fix.
*/
@Override
protected String internalAutomaticFix(PageAnalysis analysis) {
List<String[]> refTemplates = analysis.getWPCConfiguration().getStringArrayList(
WPCConfigurationStringList.REFERENCES_TEMPLATES);
if ((refTemplates == null) || (refTemplates.isEmpty())) {
return analysis.getContents();
}
return fixUsingAutomaticReplacement(analysis);
}
}