/*
* Copyright (C) 2011 4th Line GmbH, Switzerland
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fourthline.lemma.reader.content.filter;
import org.seamless.xhtml.Option;
import org.fourthline.lemma.anchor.CitationAnchor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Excludes and includes lines, depending on the given fragment rules of the citation anchor.
*
* @author Christian Bauer
*/
public class FragmentFilter implements ContentFilter {
final private Logger log = Logger.getLogger(FragmentFilter.class.getName());
final private Pattern fragmentLabelPattern;
public FragmentFilter(Pattern fragmentLabelPattern) {
this.fragmentLabelPattern = fragmentLabelPattern;
}
public Pattern getFragmentLabelPattern() {
return fragmentLabelPattern;
}
public String[] filter(String[] source, CitationAnchor citation) {
if (source == null || source.length == 0) return source;
Option includeOption = citation.getOption(CitationAnchor.OptionKey.INCLUDE);
Option excludeOption = citation.getOption(CitationAnchor.OptionKey.EXCLUDE);
Option dotsOption = citation.getOption(CitationAnchor.OptionKey.DOTS);
String[] includeFragments = includeOption != null ? includeOption.getValues() : new String[0];
String[] excludeFragments = excludeOption != null ? excludeOption.getValues() : new String[0];
boolean printDotsForExcluded = dotsOption != null ? Boolean.valueOf(dotsOption.getFirstValue()) : false;
log.fine("Filtering " + source.length + " source lines, included/excluded fragments: "
+ includeFragments.length + "/" + excludeFragments.length);
List<Integer> includedLines = new ArrayList();
if (includeFragments.length == 0) {
// Include ALL
for (int i = 0; i < source.length; i++) {
includedLines.add(i);
}
} else {
// Include fragment blocks
includedLines.addAll(getFragmentLines(source, includeFragments));
}
// Exclude fragment blocks
List<Integer> excludedLines = getFragmentLines(source, excludeFragments);
List<String> filtered = new ArrayList();
boolean dotsAlreadyPrinted = false;
for (int i = 0; i < source.length; i++) {
String s = source[i];
if (includedLines.contains(i)) {
if (!excludedLines.contains(i)) {
filtered.add(s);
// If content (not just whitespace) has been added, print dots again
if (!s.matches("\\s*")) {
dotsAlreadyPrinted = false;
}
} else if (excludedLines.contains(i) && printDotsForExcluded && !dotsAlreadyPrinted) {
// Print some ... instead of the excluded content, indentation needs to be observed
StringBuilder sb = new StringBuilder();
for (int x = 0; x< s.toCharArray().length; x++) {
char c = s.toCharArray()[x];
if (!Character.isWhitespace(c))
break;
sb.append(c);
}
sb.append("...");
filtered.add(sb.toString());
dotsAlreadyPrinted = true;
}
}
}
return filtered.toArray(new String[filtered.size()]);
}
protected List<Integer> getFragmentLines(String[] source, String[] fragments) {
List<Integer> lines = new ArrayList();
// Include only lines that are inside a block that BEGINs/ENDs with a fragment label; as
// a special case, non-closed blocks are also supported for the most common case when
// only a single line is actually marked.
for (String fragment : fragments) {
List<Integer> fragmentLines = new ArrayList();
boolean inBlock = false;
int lastBegin = 0;
for (int line = 0; line < source.length; line++) {
String s = source[line];
if (isLineMatchingFragment(s, fragment)) {
if (!inBlock) {
// BEGIN the block demarcated by the fragment label
inBlock = true;
lastBegin = line;
} else {
// END the block demarcated by the fragment label
inBlock = false;
// Add last line of block
fragmentLines.add(line);
}
}
// If we are inside a BEGIN/END fragment block, add the line
if (inBlock) {
fragmentLines.add(line);
}
// If the last block was not ENDed and we have reached the end, roll back to the
// last BEGIN but include that line. In other words: Enable single-line blocks
if (line == source.length - 1 && inBlock) {
Iterator<Integer> it = fragmentLines.iterator();
while (it.hasNext()) {
if (it.next() > lastBegin) it.remove();
}
}
}
lines.addAll(fragmentLines);
}
return lines;
}
protected boolean isLineMatchingFragment(String line, String fragment) {
Matcher m = getFragmentLabelPattern().matcher(line);
return m.matches() && m.group(2).equals(fragment);
}
}