package org.kefirsf.bb.proc; import org.kefirsf.bb.TextProcessorFactoryException; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Set; /** * bb-code scope. Required for tables, for example. * Scope contains code set for parsing text. * * @author Vitaliy Samolovskih aka Kefir */ public class ProcScope { /** * Name of scope */ private final String name; /** * Parent scope for inherit codes */ private ProcScope parent = null; /** * Code set for current scope without parent scope codes */ private Set<ProcCode> scopeCodes = null; /** * If it is true then only codes of this scope are permitted. * When the parser meets a text or non scope code the parser stops parsing. */ private boolean strong = false; /** * Mark that not parseable text must not append to result */ private boolean ignoreText = false; /** * Code of scope include the parent codes */ private ProcCode[] cachedCodes = null; /** * Mark that this scope is initialized */ private boolean initializationStarted = false; private boolean initialized = false; /** * This scope has codes with pattern which starts from non constant pattern elements. */ private boolean hasCrazyCode = false; /** * This scope has codes with variable action check. */ private boolean hasCheck = false; /** * The minimal count vocdes in the text. */ private int min = -1; /** * Maximum codes to parse */ private int max = -1; /** * Create scope * * @param name name of scope */ public ProcScope(String name) { this.name = name; } /** * Парсит тект с BB-кодами * * @param context the parsing context * @return true if parsing is success. False otherwise, If count of codes in text is not enough, for example. * @throws NestingException if nesting is too big. */ public boolean process(Context context) throws NestingException { Source source = context.getSource(); int count = 0; while (source.hasNext() && (strong || context.hasNextAdjustedForTerminator()) && (max < 0 || count < max)) { int offset = source.getOffset(); boolean parsed = false; if ((source.nextMayBeConstant() || hasCrazyCode) && !context.checkBadTag(offset)) { boolean suspicious = false; for (ProcCode code : cachedCodes) { if (code.suspicious(context)) { suspicious = true; if (code.process(context)) { parsed = true; break; } } } if (suspicious && !parsed && !hasCheck) { context.addBadTag(offset); } } if (!parsed) { if (strong) { // If scope is strong and has not a code from scope then stop the scope processing break; } else if (ignoreText) { source.incOffset(); } else { try { context.getTarget().append(source.next()); } catch (IOException e) { // Nothing! Because StringBuilder doesn't catch IOException } } } else { count++; } } return min < 0 || count >= min; } /** * Set parent scope * * @param parent parent scope. All parent scope code added to scope codes. */ public void setParent(ProcScope parent) { this.parent = parent; } /** * Add codes to scope * * @param codes code set */ public void setScopeCodes(Set<ProcCode> codes) { this.scopeCodes = codes; } public void init() { if (initializationStarted && !initialized) { throw new TextProcessorFactoryException("Can't init scope."); } else { initializationStarted = true; } if (parent != null && !parent.isInitialized()) { parent.init(); } cacheCodes(); initialized = true; } /** * Return all scope codes include parent codes. * * @return list of codes in priority order. */ private ProcCode[] getCodes() { if (!initialized) { throw new IllegalStateException("Scope is not initialized."); } return cachedCodes; } /** * Cache scope codes. Join scope codes with parent scope codes. */ private void cacheCodes() { Set<ProcCode> set = new HashSet<ProcCode>(); if (parent != null) { set.addAll(Arrays.asList(parent.getCodes())); } if (scopeCodes != null) { set.addAll(scopeCodes); } cachedCodes = set.toArray(new ProcCode[set.size()]); Arrays.sort( cachedCodes, new Comparator<ProcCode>() { public int compare(ProcCode code1, ProcCode code2) { return code2.compareTo(code1); } } ); for (ProcCode code : cachedCodes) { hasCrazyCode = hasCrazyCode || !code.startsWithConstant(); hasCheck = hasCheck || code.containsCheck(); } } public void setStrong(boolean strong) { this.strong = strong; } /** * Set flag marked that not parsiable text mustn't append to result. By default it is false. * * @param ignoreText flag value */ public void setIgnoreText(boolean ignoreText) { this.ignoreText = ignoreText; } /** * @return true if scope was initialised, false otherwise */ public boolean isInitialized() { return initialized; } public void setMin(int min) { this.min = min; } public void setMax(int max) { this.max = max; } @Override public String toString() { return name; } }