0001: /*
0002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
0003: * Distributed under the terms of either:
0004: * - the common development and distribution license (CDDL), v1.0; or
0005: * - the GNU Lesser General Public License, v2.1 or later
0006: * $Id: Parser.java 3782 2007-06-11 11:01:53Z gbevin $
0007: */
0008: package com.uwyn.rife.template;
0009:
0010: import com.uwyn.rife.template.exceptions.*;
0011: import java.util.*;
0012:
0013: import com.uwyn.rife.config.RifeConfig;
0014: import com.uwyn.rife.datastructures.DocumentPosition;
0015: import com.uwyn.rife.pcj.list.IntArrayList;
0016: import com.uwyn.rife.resources.ResourceFinder;
0017: import com.uwyn.rife.resources.exceptions.ResourceFinderErrorException;
0018: import com.uwyn.rife.tools.StringUtils;
0019: import java.io.ByteArrayOutputStream;
0020: import java.io.UnsupportedEncodingException;
0021: import java.net.URL;
0022: import java.util.regex.Matcher;
0023: import java.util.regex.Pattern;
0024:
0025: public class Parser implements Cloneable {
0026: public enum PartType {
0027: CONTENT, TAG_START, TAG_END, TAG_TERM, TAG_SHORT_TERM, STRING_DELIMITER_BEGIN, STRING_DELIMITER_END, VALUE, BLOCK, BLOCKVALUE, BLOCKAPPEND, COMMENT, INCLUDE, UNESCAPE_START
0028: }
0029:
0030: public final static String DEFAULT_TEMPLATES_PATH = "templates/";
0031:
0032: public final static String TEMPLATE_PACKAGE = "com.uwyn.rife.template.";
0033:
0034: private TemplateFactory mTemplateFactory = null;
0035:
0036: private String mIdentifier = null;
0037: private String mPackageName = null;
0038:
0039: private Config[] mConfigs = null;
0040:
0041: private String mAmbiguousName = null;
0042: private String mExtension = null;
0043: private int mExtensionLength = -1;
0044:
0045: private Pattern[] mBlockFilters = null;
0046: private Pattern[] mValueFilters = null;
0047:
0048: Parser(TemplateFactory templateFactory, String identifier,
0049: Config[] configs, String extension, Pattern[] blockFilters,
0050: Pattern[] valueFilters) {
0051: init(templateFactory, identifier, configs, extension,
0052: blockFilters, valueFilters);
0053: }
0054:
0055: Config[] getConfigs() {
0056: return mConfigs;
0057: }
0058:
0059: String getExtension() {
0060: return mExtension;
0061: }
0062:
0063: Pattern[] getBlockFilters() {
0064: return mBlockFilters;
0065: }
0066:
0067: Pattern[] getValueFilters() {
0068: return mValueFilters;
0069: }
0070:
0071: TemplateFactory getTemplateFactory() {
0072: return mTemplateFactory;
0073: }
0074:
0075: private void init(TemplateFactory templateFactory,
0076: String identifier, Config[] configs, String extension,
0077: Pattern[] blockFilters, Pattern[] valueFilters) {
0078: assert templateFactory != null;
0079: assert identifier != null;
0080: assert configs != null;
0081: assert configs.length > 0;
0082: assert extension != null;
0083:
0084: mTemplateFactory = templateFactory;
0085:
0086: mIdentifier = identifier;
0087: mPackageName = TEMPLATE_PACKAGE + mIdentifier + ".";
0088:
0089: mConfigs = configs;
0090:
0091: mExtension = extension;
0092: mExtensionLength = mExtension.length();
0093: mAmbiguousName = (mExtension + mExtension).substring(1);
0094:
0095: mBlockFilters = blockFilters;
0096: mValueFilters = valueFilters;
0097:
0098: assert mExtensionLength > 0;
0099: }
0100:
0101: public Parser clone() {
0102: Parser new_parser = null;
0103: try {
0104: new_parser = (Parser) super .clone();
0105: } catch (CloneNotSupportedException e) {
0106: new_parser = null;
0107: }
0108:
0109: Pattern[] new_blockfilters = null;
0110: if (mBlockFilters != null) {
0111: new_blockfilters = mBlockFilters.clone();
0112: }
0113: Pattern[] new_valuefilters = null;
0114: if (mValueFilters != null) {
0115: new_valuefilters = mValueFilters.clone();
0116: }
0117: new_parser.init(mTemplateFactory, mIdentifier, mConfigs,
0118: mExtension, new_blockfilters, new_valuefilters);
0119:
0120: return new_parser;
0121: }
0122:
0123: public boolean equals(Object object) {
0124: if (object == this ) {
0125: return true;
0126: }
0127:
0128: if (null == object) {
0129: return false;
0130: }
0131:
0132: if (!(object instanceof Parser)) {
0133: return false;
0134: }
0135:
0136: Parser other_parser = (Parser) object;
0137: if (!Arrays.equals(other_parser.mConfigs, this .mConfigs)) {
0138: return false;
0139: }
0140: if (!other_parser.mIdentifier.equals(this .mIdentifier)) {
0141: return false;
0142: }
0143: if (!other_parser.mExtension.equals(this .mExtension)) {
0144: return false;
0145: }
0146:
0147: if (null == other_parser.mBlockFilters
0148: && this .mBlockFilters != null
0149: || other_parser.mBlockFilters != null
0150: && null == this .mBlockFilters) {
0151: return false;
0152: }
0153: if (other_parser.mBlockFilters != null
0154: && this .mBlockFilters != null) {
0155: if (other_parser.mBlockFilters.length != this .mBlockFilters.length) {
0156: return false;
0157: }
0158:
0159: for (int i = 0; i < other_parser.mBlockFilters.length; i++) {
0160: if (!other_parser.mBlockFilters[i].pattern().equals(
0161: this .mBlockFilters[i].pattern())) {
0162: return false;
0163: }
0164: }
0165: }
0166:
0167: if (null == other_parser.mValueFilters
0168: && this .mValueFilters != null
0169: || other_parser.mValueFilters != null
0170: && null == this .mValueFilters) {
0171: return false;
0172: }
0173: if (other_parser.mValueFilters != null
0174: && this .mValueFilters != null) {
0175: if (other_parser.mValueFilters.length != this .mValueFilters.length) {
0176: return false;
0177: }
0178:
0179: for (int i = 0; i < other_parser.mValueFilters.length; i++) {
0180: if (!other_parser.mValueFilters[i].pattern().equals(
0181: this .mValueFilters[i].pattern())) {
0182: return false;
0183: }
0184: }
0185: }
0186:
0187: return true;
0188: }
0189:
0190: public Parsed parse(String name, String encoding,
0191: TemplateTransformer transformer) throws TemplateException {
0192: if (null == name)
0193: throw new IllegalArgumentException("name can't be null.");
0194:
0195: URL resource = resolve(name);
0196: if (null == resource) {
0197: throw new TemplateNotFoundException(name, null);
0198: }
0199:
0200: return parse(prepare(name, resource), encoding, transformer);
0201: }
0202:
0203: public URL resolve(String name) {
0204: if (null == name)
0205: throw new IllegalArgumentException("name can't be null.");
0206:
0207: if (0 == name.indexOf(getPackage())) {
0208: name = name.substring(getPackage().length());
0209: }
0210: name = name.replace('.', '/') + mExtension;
0211:
0212: URL resource = mTemplateFactory.getResourceFinder()
0213: .getResource(name);
0214: if (null == resource) {
0215: resource = mTemplateFactory.getResourceFinder()
0216: .getResource(DEFAULT_TEMPLATES_PATH + name);
0217: }
0218:
0219: return resource;
0220: }
0221:
0222: public String getPackage() {
0223: return mPackageName;
0224: }
0225:
0226: String escapeClassname(String name) {
0227: assert name != null;
0228:
0229: if (name.equals(mAmbiguousName)) {
0230: throw new AmbiguousTemplateNameException(name);
0231: }
0232:
0233: if (name.endsWith(mExtension)) {
0234: name = name.substring(0, name.length() - mExtensionLength);
0235: }
0236:
0237: char[] name_chars = name.toCharArray();
0238: int char_code;
0239: for (int i = 0; i < name_chars.length; i++) {
0240: char_code = name_chars[i];
0241: if ((char_code >= 48 && char_code <= 57)
0242: || (char_code >= 65 && char_code <= 90)
0243: || (char_code >= 97 && char_code <= 122)
0244: || char_code == 46) {
0245: continue;
0246: }
0247:
0248: if (char_code == '/' || char_code == '\\') {
0249: name_chars[i] = '.';
0250: } else {
0251: name_chars[i] = '_';
0252: }
0253: }
0254:
0255: return new String(name_chars);
0256: }
0257:
0258: public Parsed prepare(String name, URL resource) {
0259: if (null == name)
0260: throw new IllegalArgumentException("name can't be null.");
0261: if (null == resource)
0262: throw new IllegalArgumentException(
0263: "resource can't be null.");
0264:
0265: String template_name = name;
0266: Parsed template_parsed = new Parsed(this );
0267: if (0 == template_name.indexOf(getPackage())) {
0268: template_name = name.substring(getPackage().length());
0269: }
0270:
0271: String class_name = template_name;
0272: String subpackage = "";
0273: int package_seperator = template_name.lastIndexOf(".");
0274: if (package_seperator != -1) {
0275: subpackage = "."
0276: + template_name.substring(0, package_seperator);
0277: class_name = template_name.substring(package_seperator + 1);
0278: }
0279: template_parsed.setTemplateName(template_name);
0280: template_parsed.setPackage(getPackage().substring(0,
0281: getPackage().length() - 1)
0282: + subpackage);
0283: template_parsed.setClassName(escapeClassname(class_name));
0284: template_parsed.setResource(resource);
0285:
0286: return template_parsed;
0287: }
0288:
0289: Parsed parse(Parsed parsed, String encoding,
0290: TemplateTransformer transformer) throws TemplateException {
0291: assert parsed != null;
0292:
0293: if (null == encoding) {
0294: encoding = RifeConfig.Template.getDefaultEncoding();
0295: }
0296:
0297: // get the resource of the template file
0298: URL resource = parsed.getResource();
0299:
0300: // obtain the content of the template file
0301: StringBuilder content_buffer = getContent(parsed
0302: .getTemplateName(), parsed, resource, encoding,
0303: transformer);
0304:
0305: // replace the included templates
0306: Stack<String> previous_includes = new Stack<String>();
0307: previous_includes.push(parsed.getFullClassName());
0308: replaceIncludeTags(parsed, content_buffer, previous_includes,
0309: encoding, transformer);
0310: previous_includes.pop();
0311:
0312: // process the blocks and values
0313: String content = content_buffer.toString();
0314: parseBlocks(parsed, content);
0315: parsed.setFilteredBlocks(filterTags(mBlockFilters, parsed
0316: .getBlockIds()));
0317: parsed.setFilteredValues(filterTags(mValueFilters, parsed
0318: .getValueIds()));
0319:
0320: assert parsed.getBlocks().size() >= 1;
0321:
0322: return parsed;
0323: }
0324:
0325: private StringBuilder getContent(String templateName,
0326: Parsed parsed, URL resource, String encoding,
0327: TemplateTransformer transformer) throws TemplateException {
0328: if (null == transformer) {
0329: return getFileContent(resource, encoding);
0330: } else {
0331: return getTransformedContent(templateName, parsed,
0332: resource, encoding, transformer);
0333: }
0334: }
0335:
0336: private StringBuilder getFileContent(URL resource, String encoding)
0337: throws TemplateException {
0338: assert resource != null;
0339:
0340: String content = null;
0341:
0342: try {
0343: content = mTemplateFactory.getResourceFinder().getContent(
0344: resource, encoding);
0345: } catch (ResourceFinderErrorException e) {
0346: throw new GetContentErrorException(resource
0347: .toExternalForm(), e);
0348: }
0349:
0350: return new StringBuilder(content);
0351: }
0352:
0353: private StringBuilder getTransformedContent(String templateName,
0354: Parsed parsed, URL resource, String encoding,
0355: TemplateTransformer transformer) throws TemplateException {
0356: assert resource != null;
0357:
0358: ByteArrayOutputStream result = new ByteArrayOutputStream();
0359:
0360: // transform the content
0361: transformer.setResourceFinder(mTemplateFactory
0362: .getResourceFinder());
0363: Collection<URL> dependencies = transformer.transform(
0364: templateName, resource, result, encoding);
0365: // get the dependencies and their modification times
0366: if (dependencies != null && dependencies.size() > 0) {
0367: long modification_time = 0;
0368: for (URL dependency_resource : dependencies) {
0369: try {
0370: modification_time = transformer.getResourceFinder()
0371: .getModificationTime(dependency_resource);
0372: } catch (ResourceFinderErrorException e) {
0373: // if there was trouble in obtaining the modification time, just set
0374: // it to 0 so that the dependency will always be outdated
0375: modification_time = 0;
0376: }
0377:
0378: if (modification_time > 0) {
0379: parsed.addDependency(dependency_resource, new Long(
0380: modification_time));
0381: }
0382: }
0383: }
0384: // set the modification state so that different filter configurations
0385: // will reload the template, not only modifications to the dependencies
0386: parsed.setModificationState(transformer.getState());
0387:
0388: // convert the result to a StringBuilder
0389: try {
0390: if (null == encoding) {
0391: if (null == transformer.getEncoding()) {
0392: return new StringBuilder(result.toString());
0393: } else {
0394: return new StringBuilder(result
0395: .toString(transformer.getEncoding()));
0396: }
0397: } else {
0398: return new StringBuilder(result.toString(encoding));
0399: }
0400: } catch (UnsupportedEncodingException e) {
0401: throw new TransformedResultConversionException(resource
0402: .toExternalForm(), e);
0403: }
0404: }
0405:
0406: public static long getModificationTime(
0407: ResourceFinder resourceFinder, URL resource)
0408: throws TemplateException {
0409: if (null == resource)
0410: throw new IllegalArgumentException(
0411: "resource can't be null.");
0412:
0413: long modification_time = -1;
0414:
0415: try {
0416: modification_time = resourceFinder
0417: .getModificationTime(resource);
0418: } catch (ResourceFinderErrorException e) {
0419: throw new ModificationTimeErrorException(resource
0420: .toExternalForm(), e);
0421: }
0422:
0423: return modification_time;
0424: }
0425:
0426: private int forwardToNextWhitespace(int start, String content) {
0427: // forward until the first non whitespace character
0428: int i = start;
0429: for (; i < content.length(); i++) {
0430: if (!Character.isWhitespace(content.charAt(i))) {
0431: break;
0432: }
0433: }
0434:
0435: return i;
0436: }
0437:
0438: /**
0439: * Get the next indices of a search string in the content by taking
0440: * backslash escaping into account and skipping over those search
0441: * strings that are escaped by it. If fixed parts are provided it will also
0442: * ensure that the search string is followed by them and the text in between
0443: * is only whitespace.
0444: */
0445: private TagMatch getEscapedIndex(String content, String search,
0446: int start, ConfigPart... fixedParts) {
0447: TagMatch match = new TagMatch();
0448:
0449: int begin_index = content.indexOf(search, start);
0450:
0451: if (begin_index != -1) {
0452: int ending_index = -1;
0453: int last_match = begin_index;
0454: int last_ending_index = -1;
0455: while (begin_index != -1) {
0456: match.clear();
0457:
0458: match.addMatch(begin_index);
0459:
0460: if (begin_index > 0
0461: && '\\' == content.charAt(begin_index - 1) && // check for a first escaping backslash
0462: (begin_index < 2 || content
0463: .charAt(begin_index - 2) != '\\')) // check if that one hasn't been escaped itself
0464: {
0465: begin_index = -1;
0466: }
0467: // if fixed parts have been provided, ensure that they are available
0468: // after the last match and that the text between them is only
0469: // whitespace
0470: else if (fixedParts != null && fixedParts.length > 0) {
0471: last_ending_index = begin_index + search.length();
0472: for (ConfigPart fixed_part : fixedParts) {
0473: if (0 == fixed_part.length()) {
0474: match.addMatch(forwardToNextWhitespace(
0475: last_ending_index, content));
0476: match.addFixedPartMatched(true);
0477: continue;
0478: }
0479:
0480: ending_index = content.indexOf(fixed_part
0481: .getText(), last_ending_index);
0482: String seperating_text = null;
0483: String seperating_text_trim = null;
0484: if (ending_index != -1) {
0485: seperating_text = content.substring(
0486: last_ending_index, ending_index);
0487: seperating_text_trim = seperating_text
0488: .trim();
0489: }
0490: if (-1 == ending_index
0491: || seperating_text_trim.length() != 0) {
0492: if (fixed_part.isOptional()
0493: && (null == seperating_text || seperating_text
0494: .indexOf(seperating_text_trim) > 0)) {
0495: match.addMatch(forwardToNextWhitespace(
0496: last_ending_index, content));
0497: match.addFixedPartMatched(false);
0498: continue;
0499: } else {
0500: begin_index = -1;
0501: break;
0502: }
0503: }
0504:
0505: last_ending_index = ending_index
0506: + fixed_part.length();
0507: match.addMatch(last_ending_index);
0508: match.addFixedPartMatched(true);
0509: }
0510: }
0511:
0512: // continue searching if the match wasn't successful
0513: if (-1 == begin_index) {
0514: begin_index = content.indexOf(search,
0515: last_match + 1);
0516: last_match = begin_index;
0517: continue;
0518: }
0519:
0520: return match;
0521: }
0522: }
0523:
0524: // return negative result
0525: return null;
0526: }
0527:
0528: /**
0529: * Construct an array with only the configurations that need to be unescaped
0530: */
0531: private Parser.Config[] getUnescapeConfigs(String content) {
0532: // iterate over the supported parser configurations to find the ones
0533: // that are unused
0534: List<Parser.Config> configs_list = new ArrayList<Parser.Config>();
0535: for (Parser.Config config : mConfigs) {
0536: if (content.indexOf(config.mUnescapeStart.getText()) != -1) {
0537: configs_list.add(config);
0538: }
0539: }
0540:
0541: Parser.Config[] configs = null;
0542: if (configs_list.size() > 0) {
0543: // make a config array, containing only the used configs
0544: configs = new Parser.Config[configs_list.size()];
0545: configs_list.toArray(configs);
0546: }
0547:
0548: return configs;
0549: }
0550:
0551: /**
0552: * Removes the single escape backslash character from content
0553: */
0554: private String unescapePart(Parser.Config[] configs, String part) {
0555: if (configs != null) {
0556: for (Parser.Config config : configs) {
0557: // minor optimization to only do regexp unescape matching when a
0558: // matching possibility is present through a quick lookup
0559: if (part.indexOf(config.mUnescapeStart.getText()) != -1) {
0560: part = config.mUnescapePattern.matcher(part)
0561: .replaceAll("$1");
0562: }
0563: }
0564: }
0565:
0566: part = removeTrailingDoubleEscape(part, 0, part.length());
0567:
0568: return part;
0569: }
0570:
0571: /**
0572: * Replaces the double escaping backslashes from the end of block content
0573: * by a single backslash
0574: */
0575: private String removeTrailingDoubleEscape(String content,
0576: int begin, int end) {
0577: if (end >= 2 && '\\' == content.charAt(end - 1)
0578: && '\\' == content.charAt(end - 2)) {
0579: return content.substring(begin, end - 1);
0580: } else {
0581: return content.substring(begin, end);
0582: }
0583: }
0584:
0585: /**
0586: * Looks for the first match of a config part text in the text content. If
0587: * a part doesn't match and is optional, the next one will be tried. If the
0588: * part is empty, the next one will always be tried immediately.
0589: **/
0590: private ConfigPartMatch getFirstFoundPartIndex(String content,
0591: int startIndex, ConfigPart... parts) {
0592: for (ConfigPart part : parts) {
0593: if (part.length() != 0) {
0594: int result = content
0595: .indexOf(part.getText(), startIndex);
0596: if (result != -1) {
0597: return new ConfigPartMatch(result, part);
0598: } else if (!part.isOptional()) {
0599: return ConfigPartMatch.NO_MATCH;
0600: }
0601: }
0602: }
0603:
0604: return ConfigPartMatch.NO_MATCH;
0605: }
0606:
0607: private void replaceIncludeTags(Parsed parsed,
0608: StringBuilder content, Stack<String> previousIncludes,
0609: String encoding, TemplateTransformer transformer)
0610: throws TemplateException {
0611: assert parsed != null;
0612: assert content != null;
0613: assert previousIncludes != null;
0614:
0615: TagMatch tag_match = null;
0616: ConfigPartMatch part_match = null;
0617:
0618: int previous_index = 0;
0619: int begin_start_index = 0;
0620: int includename_end_index = 0;
0621: int term_start_index = 0;
0622: int tag_end_index = 0;
0623: int includename_begin_index = 0;
0624:
0625: boolean begin_isquoted = false;
0626:
0627: String included_template_name = null;
0628: URL included_template_resource = null;
0629: Parsed included_template_parsed = null;
0630: StringBuilder included_template_content = null;
0631:
0632: // process the included files
0633: // and iterate over the support parser configurations
0634: for (Parser.Config config : mConfigs) {
0635: do {
0636: // find the begin position of the include tag
0637: tag_match = getEscapedIndex(content.toString(),
0638: config.mTagStart.getText(), previous_index,
0639: config.mIncludeTag,
0640: config.mStringDelimiterBegin);
0641:
0642: if (tag_match != null) {
0643: begin_start_index = tag_match.getMatch(0);
0644:
0645: // find the begin of the filename
0646: includename_begin_index = tag_match.getMatch(2);
0647: begin_isquoted = tag_match.didFixedPartMatch(1);
0648: part_match = null;
0649: includename_end_index = -1;
0650:
0651: // find the string delimiter
0652: // get the string delimiter that marks the end of the value id
0653: int includename_end_offset = 0;
0654: if (begin_isquoted) {
0655: part_match = getFirstFoundPartIndex(content
0656: .toString(), includename_begin_index,
0657: config.mStringDelimiterEnd);
0658: }
0659:
0660: if (part_match != null && part_match.mPart != null) {
0661: includename_end_index = part_match.mIndex;
0662: includename_end_offset = part_match.mPart
0663: .length();
0664: }
0665: // ensure that a name that started out quoted is already terminated with a delimiter
0666: else if (begin_isquoted && null == part_match.mPart) {
0667: throw new AttributeNotEndedException(parsed
0668: .getClassName(), StringUtils
0669: .getDocumentPosition(
0670: content.toString(),
0671: includename_begin_index),
0672: config.mIncludeTag.getText(), "name");
0673: } else {
0674: int short_index = content.indexOf(
0675: config.mTagShortTerm.getText(),
0676: includename_begin_index);
0677: includename_end_offset = 0;
0678: includename_end_index = backtrackTillFirstNonWhitespace(
0679: content.toString(), short_index);
0680: }
0681:
0682: // check if the include name was ended
0683: if (-1 == includename_end_index)
0684: break;
0685:
0686: // obtain the filename
0687: included_template_name = content.substring(
0688: includename_begin_index,
0689: includename_end_index);
0690:
0691: // ensure that an end delimiter corresponds to a start delimiter
0692: if (!begin_isquoted
0693: && included_template_name
0694: .endsWith(config.mStringDelimiterEnd
0695: .getText())) {
0696: throw new AttributeWronglyEndedException(
0697: parsed.getClassName(),
0698: StringUtils
0699: .getDocumentPosition(
0700: content.toString(),
0701: includename_end_index
0702: + includename_end_offset
0703: - config.mStringDelimiterEnd
0704: .length()),
0705: config.mIncludeTag.getText(), "name");
0706: }
0707:
0708: // get the first tag ending
0709: term_start_index = content.indexOf(
0710: config.mTagShortTerm.getText(),
0711: includename_end_index
0712: + includename_end_offset);
0713:
0714: // ensure that the tag was propertly ended
0715: if (-1 == term_start_index)
0716: break;
0717:
0718: // check that there's only whitespace between the delimiter and the start of the termination tag
0719: if (content.substring(
0720: includename_end_index
0721: + includename_end_offset,
0722: term_start_index).trim().length() > 0) {
0723: throw new TagBadlyTerminatedException(parsed
0724: .getClassName(), StringUtils
0725: .getDocumentPosition(
0726: content.toString(),
0727: term_start_index),
0728: config.mIncludeTag.getText(),
0729: included_template_name);
0730: }
0731:
0732: // calculate the index of the end of the complete tag
0733: tag_end_index = term_start_index
0734: + config.mTagShortTermLength;
0735:
0736: // obtain the parser that will be used to get the included content
0737: Parser include_parser = this ;
0738: // check if the included template references another template type
0739: int doublecolon_index = included_template_name
0740: .indexOf(':');
0741: if (doublecolon_index != -1) {
0742: String template_type = included_template_name
0743: .substring(0, doublecolon_index);
0744: if (!template_type.equals(mTemplateFactory
0745: .toString())) {
0746: TemplateFactory factory = TemplateFactory
0747: .getFactory(template_type);
0748: include_parser = factory.getParser();
0749: included_template_name = included_template_name
0750: .substring(doublecolon_index + 1);
0751: }
0752: }
0753:
0754: included_template_resource = include_parser
0755: .resolve(included_template_name);
0756: if (null == included_template_resource) {
0757: throw new IncludeNotFoundException(parsed
0758: .getClassName(), StringUtils
0759: .getDocumentPosition(
0760: content.toString(),
0761: begin_start_index),
0762: included_template_name);
0763: }
0764: included_template_parsed = include_parser.prepare(
0765: included_template_name,
0766: included_template_resource);
0767:
0768: // check for circular references
0769: if (previousIncludes
0770: .contains(included_template_parsed
0771: .getFullClassName())) {
0772: throw new CircularIncludesException(parsed
0773: .getClassName(), StringUtils
0774: .getDocumentPosition(
0775: content.toString(),
0776: begin_start_index),
0777: included_template_name,
0778: previousIncludes);
0779: }
0780:
0781: // parse the included template's include tags too
0782: included_template_content = include_parser
0783: .getContent(included_template_name, parsed,
0784: included_template_parsed
0785: .getResource(), encoding,
0786: transformer);
0787: previousIncludes.push(included_template_parsed
0788: .getFullClassName());
0789: replaceIncludeTags(included_template_parsed,
0790: included_template_content,
0791: previousIncludes, encoding, transformer);
0792: previousIncludes.pop();
0793:
0794: // replace the tag with the included file's content
0795: // if there's a double escaping backslash before the tag, it
0796: // will also be unescaped
0797: if (begin_start_index >= 2
0798: && '\\' == content
0799: .charAt(begin_start_index - 1)
0800: && '\\' == content
0801: .charAt(begin_start_index - 2)) {
0802: content.replace(begin_start_index - 1,
0803: tag_end_index,
0804: included_template_content.toString());
0805: } else {
0806: content.replace(begin_start_index,
0807: tag_end_index,
0808: included_template_content.toString());
0809: }
0810:
0811: // retain the link to this include file for optional later modification time checking
0812: parsed.addDependency(included_template_parsed);
0813:
0814: // add the dependencies of the included template too
0815: Map<URL, Long> included_dependencies = included_template_parsed
0816: .getDependencies();
0817: for (Map.Entry<URL, Long> included_dependency : included_dependencies
0818: .entrySet()) {
0819: parsed.addDependency(included_dependency
0820: .getKey(), included_dependency
0821: .getValue());
0822: }
0823:
0824: // continue the search after this tag
0825: previous_index = begin_start_index;
0826: }
0827: } while (tag_match != null);
0828: }
0829: }
0830:
0831: private TagMatch getFirstMatch(TagMatch... matches) {
0832: if (null == matches || 0 == matches.length) {
0833: return null;
0834: }
0835:
0836: TagMatch first_match = null;
0837: for (TagMatch match : matches) {
0838: if (match != null) {
0839: int candidate = match.getMatch(0);
0840: if (candidate != -1) {
0841: if (null == first_match) {
0842: first_match = match;
0843: } else {
0844: if (candidate <= first_match.getMatch(0)) {
0845: first_match = match;
0846: }
0847: }
0848: }
0849: }
0850: }
0851:
0852: return first_match;
0853: }
0854:
0855: private int getFirstMatch(int... candidates) {
0856: if (null == candidates || 0 == candidates.length) {
0857: return -1;
0858: }
0859:
0860: int first_candidate = -1;
0861: for (int candidate : candidates) {
0862: if (candidate != -1) {
0863: if (-1 == first_candidate) {
0864: first_candidate = candidate;
0865: } else {
0866: if (candidate <= first_candidate) {
0867: first_candidate = candidate;
0868: }
0869: }
0870: }
0871: }
0872:
0873: return first_candidate;
0874: }
0875:
0876: private int backtrackTillFirstNonWhitespace(String content,
0877: int index) {
0878: if (-1 == index) {
0879: return -1;
0880: } else {
0881: do {
0882: index--;
0883:
0884: if (!Character.isWhitespace(content.charAt(index))) {
0885: break;
0886: }
0887: } while (index >= 0);
0888:
0889: return index + 1;
0890: }
0891: }
0892:
0893: private void parseBlocks(Parsed parsed, String content)
0894: throws TemplateException {
0895: assert parsed != null;
0896: assert content != null;
0897:
0898: LinkedHashMap<String, StringBuilder> blocks = new LinkedHashMap<String, StringBuilder>();
0899: blocks.put("", new StringBuilder(""));
0900:
0901: // iterate over the supported parser configurations to find the ones
0902: // that are unused
0903: List<Parser.Config> block_configs_list = new ArrayList<Parser.Config>();
0904: for (Parser.Config config : mConfigs) {
0905: if (getEscapedIndex(content, config.mTagStart.getText(), 0,
0906: config.mBlockTag, config.mStringDelimiterBegin) != null
0907: || getEscapedIndex(content, config.mTagStart
0908: .getText(), 0, config.mBlockvalueTag,
0909: config.mStringDelimiterBegin) != null
0910: || getEscapedIndex(content, config.mTagStart
0911: .getText(), 0, config.mBlockappendTag,
0912: config.mStringDelimiterBegin) != null
0913: || getEscapedIndex(content, config.mTagTerm
0914: .getText(), 0, config.mBlockTag,
0915: config.mTagEnd) != null
0916: || getEscapedIndex(content, config.mTagTerm
0917: .getText(), 0, config.mBlockvalueTag,
0918: config.mTagEnd) != null
0919: || getEscapedIndex(content, config.mTagTerm
0920: .getText(), 0, config.mBlockappendTag,
0921: config.mTagEnd) != null
0922: || getEscapedIndex(content, config.mTagStart
0923: .getText(), 0, config.mCommentTag,
0924: config.mTagEnd) != null
0925: || getEscapedIndex(content, config.mTagTerm
0926: .getText(), 0, config.mCommentTag,
0927: config.mTagEnd) != null) {
0928: block_configs_list.add(config);
0929: }
0930: }
0931:
0932: Parser.Config[] configs = null;
0933: int previous_index = 0;
0934: if (block_configs_list.size() > 0) {
0935: // make a config array, containing only the used configs
0936: configs = new Parser.Config[block_configs_list.size()];
0937: block_configs_list.toArray(configs);
0938:
0939: // setup the parser variables
0940: Stack<String> block_ids = new Stack<String>();
0941: Stack<ConfigPart> block_types = new Stack<ConfigPart>();
0942: Stack<Parser.Config> block_configs = new Stack<Parser.Config>();
0943:
0944: // create the root block which is the anonymous main content
0945: block_ids.push("");
0946: block_types.push(new ConfigPart(PartType.CONTENT,
0947: "CONTENT", false));
0948: block_configs.push(null);
0949:
0950: TagMatch match1 = null;
0951: TagMatch match2 = null;
0952: TagMatch match3 = null;
0953: TagMatch match4 = null;
0954: TagMatch first_match = null;
0955:
0956: int begin_start_index = 0;
0957: int delimiter_end_index = 0;
0958: int begin_end_index = 0;
0959: int term_start_index = 0;
0960: int term_end_index = 0;
0961: int blockid_start_index = 0;
0962: int blockid_end_index = 0;
0963:
0964: int[] begin_start_indices = new int[configs.length];
0965: int[] delimiter_end_indices = new int[configs.length];
0966: boolean[] begin_isblockvalue_flags = new boolean[configs.length];
0967: boolean[] begin_isblockappend_flags = new boolean[configs.length];
0968: boolean[] begin_iscomment_flags = new boolean[configs.length];
0969: boolean[] begin_isquoted_flags = new boolean[configs.length];
0970: int[] term_start_indices = new int[configs.length];
0971: int[] term_end_indices = new int[configs.length];
0972: boolean[] term_isblockvalue_flags = new boolean[configs.length];
0973: boolean[] term_isblockappend_flags = new boolean[configs.length];
0974: boolean[] term_iscomment_flags = new boolean[configs.length];
0975:
0976: String leftover_content = null;
0977: String blockid = null;
0978:
0979: Parser.Config config_begin = null;
0980: Parser.Config config_term = null;
0981:
0982: boolean begin_isblockvalue = false;
0983: boolean begin_isblockappend = false;
0984: boolean begin_iscomment = false;
0985: boolean begin_isquoted = false;
0986: boolean term_isblockvalue = false;
0987: boolean term_isblockappend = false;
0988: boolean term_iscomment = false;
0989:
0990: // process the block and content
0991: do {
0992: {
0993: String block_id_peek = block_ids.peek();
0994:
0995: // iterate over the supported parser configurations to find all
0996: // start tag beginnings
0997: for (int i = 0; i < configs.length; i++) {
0998: if (null == block_id_peek) {
0999: first_match = null;
1000: } else {
1001: match1 = getEscapedIndex(content,
1002: configs[i].mTagStart.getText(),
1003: previous_index,
1004: configs[i].mBlockTag,
1005: configs[i].mStringDelimiterBegin);
1006: match2 = getEscapedIndex(content,
1007: configs[i].mTagStart.getText(),
1008: previous_index,
1009: configs[i].mBlockvalueTag,
1010: configs[i].mStringDelimiterBegin);
1011: match3 = getEscapedIndex(content,
1012: configs[i].mTagStart.getText(),
1013: previous_index,
1014: configs[i].mBlockappendTag,
1015: configs[i].mStringDelimiterBegin);
1016: match4 = getEscapedIndex(content,
1017: configs[i].mTagStart.getText(),
1018: previous_index,
1019: configs[i].mCommentTag,
1020: configs[i].mTagEnd);
1021: first_match = getFirstMatch(match1, match2,
1022: match3, match4);
1023: }
1024:
1025: if (null == first_match) {
1026: begin_start_indices[i] = -1;
1027: begin_isblockvalue_flags[i] = false;
1028: begin_isblockappend_flags[i] = false;
1029: begin_iscomment_flags[i] = false;
1030: begin_isquoted_flags[i] = false;
1031: delimiter_end_indices[i] = -1;
1032: } else {
1033: begin_start_indices[i] = first_match
1034: .getMatch(0);
1035: begin_isblockvalue_flags[i] = (first_match == match2);
1036: begin_isblockappend_flags[i] = (first_match == match3);
1037: begin_iscomment_flags[i] = (first_match == match4);
1038: begin_isquoted_flags[i] = first_match
1039: .didFixedPartMatch(1);
1040: delimiter_end_indices[i] = first_match
1041: .getMatch(2);
1042: }
1043: }
1044:
1045: // iterate over the supported parser configurations all term tag
1046: // beginnings
1047: for (int i = 0; i < configs.length; i++) {
1048: if (null == block_id_peek) {
1049: match1 = null;
1050: match2 = null;
1051: match3 = null;
1052: } else {
1053: match1 = getEscapedIndex(content,
1054: configs[i].mTagTerm.getText(),
1055: previous_index,
1056: configs[i].mBlockTag,
1057: configs[i].mTagEnd);
1058: match2 = getEscapedIndex(content,
1059: configs[i].mTagTerm.getText(),
1060: previous_index,
1061: configs[i].mBlockvalueTag,
1062: configs[i].mTagEnd);
1063: match3 = getEscapedIndex(content,
1064: configs[i].mTagTerm.getText(),
1065: previous_index,
1066: configs[i].mBlockappendTag,
1067: configs[i].mTagEnd);
1068: }
1069: match4 = getEscapedIndex(content,
1070: configs[i].mTagTerm.getText(),
1071: previous_index, configs[i].mCommentTag,
1072: configs[i].mTagEnd);
1073: first_match = getFirstMatch(match1, match2,
1074: match3, match4);
1075:
1076: if (null == first_match) {
1077: term_start_indices[i] = -1;
1078: term_isblockvalue_flags[i] = false;
1079: term_isblockappend_flags[i] = false;
1080: term_iscomment_flags[i] = false;
1081: term_end_indices[i] = -1;
1082: } else {
1083: term_start_indices[i] = first_match
1084: .getMatch(0);
1085: term_isblockvalue_flags[i] = (first_match == match2);
1086: term_isblockappend_flags[i] = (first_match == match3);
1087: term_iscomment_flags[i] = (first_match == match4);
1088: term_end_indices[i] = first_match
1089: .getMatch(2);
1090: }
1091: }
1092: }
1093:
1094: // find the start position of the next block begin tags by comparing
1095: // which is the earliest start tag beginning
1096: config_begin = null;
1097: begin_start_index = -1;
1098: for (int i = 0; i < begin_start_indices.length; i++) {
1099: if (begin_start_indices[i] != -1
1100: && (-1 == begin_start_index || begin_start_indices[i] < begin_start_index)) {
1101: begin_start_index = begin_start_indices[i];
1102: begin_isblockvalue = begin_isblockvalue_flags[i];
1103: begin_isblockappend = begin_isblockappend_flags[i];
1104: begin_iscomment = begin_iscomment_flags[i];
1105: begin_isquoted = begin_isquoted_flags[i];
1106: delimiter_end_index = delimiter_end_indices[i];
1107: config_begin = configs[i];
1108: }
1109: }
1110:
1111: // find the start position of the next block term tags by comparing
1112: // which is the earliest term tag beginning
1113: config_term = null;
1114: term_start_index = -1;
1115: term_end_index = -1;
1116: for (int i = 0; i < term_start_indices.length; i++) {
1117: if (term_start_indices[i] != -1
1118: && (-1 == term_start_index || term_start_indices[i] < term_start_index)) {
1119: term_start_index = term_start_indices[i];
1120: term_isblockvalue = term_isblockvalue_flags[i];
1121: term_isblockappend = term_isblockappend_flags[i];
1122: term_iscomment = term_iscomment_flags[i];
1123: term_end_index = term_end_indices[i];
1124: config_term = configs[i];
1125: }
1126: }
1127:
1128: // check if a begin tag was found and if the end tag comes after it
1129: // or the end tag has been omitted which means that the blocks are nested
1130: // start the new block that corresponds to the new begin tag
1131: if (begin_start_index != -1
1132: && (begin_start_index < term_start_index || -1 == term_start_index)) {
1133: // the text upto the beginning of this block has to be extracted
1134: // check if the next begin tag has been double-escaped
1135: leftover_content = removeTrailingDoubleEscape(
1136: content, previous_index, begin_start_index);
1137:
1138: // and added to the preceeding block
1139: blockid = block_ids.peek();
1140: if (blockid != null) {
1141: blocks.get(blockid).append(leftover_content);
1142: }
1143:
1144: // check if this is a comment tag and don't extract an id in that case
1145: if (begin_iscomment) {
1146: blockid_start_index = -1;
1147: blockid_end_index = -1;
1148: begin_end_index = delimiter_end_index;
1149: blockid = null;
1150:
1151: // add this comment block to the stack of active blocks
1152: block_ids.push(null);
1153: block_types.push(config_begin.mCommentTag);
1154: block_configs.push(config_begin);
1155: } else {
1156: // get the begin and end position of the block's id
1157: blockid_start_index = delimiter_end_index;
1158:
1159: // get the string delimiter that marks the end of the block id
1160: ConfigPartMatch delimiter_end_match = null;
1161: if (begin_isquoted) {
1162: delimiter_end_match = getFirstFoundPartIndex(
1163: content, blockid_start_index,
1164: config_begin.mStringDelimiterEnd);
1165: }
1166:
1167: if (delimiter_end_match != null
1168: && delimiter_end_match.mPart != null) {
1169: blockid_end_index = delimiter_end_match.mIndex;
1170: }
1171: // ensure that a name that started out quoted is already terminated with a delimiter
1172: else if (begin_isquoted
1173: && null == delimiter_end_match.mPart) {
1174: String tag_type;
1175: if (begin_isblockvalue)
1176: tag_type = config_begin.mBlockvalueTag
1177: .getText();
1178: else if (begin_isblockappend)
1179: tag_type = config_begin.mBlockappendTag
1180: .getText();
1181: else
1182: tag_type = config_begin.mBlockTag
1183: .getText();
1184: throw new AttributeNotEndedException(parsed
1185: .getClassName(), StringUtils
1186: .getDocumentPosition(content,
1187: blockid_start_index),
1188: tag_type, "name");
1189: } else {
1190: int long_index = content.indexOf(
1191: config_begin.mTagEnd.getText(),
1192: blockid_start_index);
1193: int short_index = content.indexOf(
1194: config_begin.mTagShortTerm
1195: .getText(),
1196: blockid_start_index);
1197:
1198: // backtrack until the first non whitespace character
1199: blockid_end_index = backtrackTillFirstNonWhitespace(
1200: content, getFirstMatch(long_index,
1201: short_index));
1202: }
1203:
1204: if (-1 == blockid_end_index) {
1205: String tag_type;
1206: if (begin_isblockvalue)
1207: tag_type = config_begin.mBlockvalueTag
1208: .getText();
1209: else if (begin_isblockappend)
1210: tag_type = config_begin.mBlockappendTag
1211: .getText();
1212: else
1213: tag_type = config_begin.mBlockTag
1214: .getText();
1215: throw new AttributeNotEndedException(parsed
1216: .getClassName(), StringUtils
1217: .getDocumentPosition(content,
1218: blockid_start_index),
1219: tag_type, "name");
1220: } else {
1221: // extract the id of the block
1222: blockid = content.substring(
1223: blockid_start_index,
1224: blockid_end_index);
1225:
1226: // ensure that an end delimiter corresponds to a start delimiter
1227: if (!begin_isquoted
1228: && blockid
1229: .endsWith(config_begin.mStringDelimiterEnd
1230: .getText())) {
1231: String tag_type;
1232: if (begin_isblockvalue)
1233: tag_type = config_begin.mBlockvalueTag
1234: .getText();
1235: else if (begin_isblockappend)
1236: tag_type = config_begin.mBlockappendTag
1237: .getText();
1238: else
1239: tag_type = config_begin.mBlockTag
1240: .getText();
1241: throw new AttributeWronglyEndedException(
1242: parsed.getClassName(),
1243: StringUtils
1244: .getDocumentPosition(
1245: content,
1246: blockid_end_index
1247: - config_begin.mStringDelimiterEnd
1248: .length()),
1249: tag_type, "name");
1250: }
1251:
1252: // get the index of the end of the block begin tag
1253: begin_end_index = content.indexOf(
1254: config_begin.mTagEnd.getText(),
1255: blockid_end_index);
1256: if (-1 == begin_end_index) {
1257: String tag_type;
1258: if (begin_isblockvalue)
1259: tag_type = config_begin.mBlockvalueTag
1260: .getText();
1261: else if (begin_isblockappend)
1262: tag_type = config_begin.mBlockappendTag
1263: .getText();
1264: else
1265: tag_type = config_begin.mBlockTag
1266: .getText();
1267: throw new BeginTagNotEndedException(
1268: parsed.getClassName(),
1269: StringUtils
1270: .getDocumentPosition(
1271: content,
1272: blockid_end_index),
1273: tag_type, blockid);
1274: } else {
1275: // check that the begin tag termination is valid
1276: if (delimiter_end_match != null
1277: && delimiter_end_match.mPart != null
1278: && content
1279: .substring(
1280: blockid_end_index
1281: + delimiter_end_match.mPart
1282: .length(),
1283: begin_end_index)
1284: .trim().length() > 0) {
1285: String tag_type;
1286: if (begin_isblockvalue)
1287: tag_type = config_begin.mBlockvalueTag
1288: .getText();
1289: else if (begin_isblockappend)
1290: tag_type = config_begin.mBlockappendTag
1291: .getText();
1292: else
1293: tag_type = config_begin.mBlockTag
1294: .getText();
1295: throw new BeginTagBadlyTerminatedException(
1296: parsed.getClassName(),
1297: StringUtils
1298: .getDocumentPosition(
1299: content,
1300: blockid_end_index
1301: + config_begin.mStringDelimiterEnd
1302: .length()),
1303: tag_type, blockid);
1304: }
1305:
1306: // the start tag was correctly ended, add this block to the stack of active block ids
1307: block_ids.push(blockid);
1308: block_configs.push(config_begin);
1309:
1310: if (begin_isblockvalue) {
1311: blocks.put(blockid,
1312: new StringBuilder(""));
1313: block_types
1314: .push(config_begin.mBlockvalueTag);
1315: parsed.setBlockvalue(blockid);
1316: } else if (begin_isblockappend) {
1317: StringBuilder current_block = blocks
1318: .get(blockid);
1319: if (null == current_block) {
1320: current_block = new StringBuilder(
1321: "");
1322: blocks.put(blockid,
1323: current_block);
1324: }
1325: block_types
1326: .push(config_begin.mBlockappendTag);
1327: parsed.setBlockvalue(blockid);
1328: } else {
1329: blocks.put(blockid,
1330: new StringBuilder(""));
1331: block_types
1332: .push(config_begin.mBlockTag);
1333: }
1334: }
1335: }
1336: }
1337:
1338: // continue the search after this tag
1339: previous_index = begin_end_index
1340: + config_begin.mTagEndLength;
1341: }
1342: // the termination tag came before the end tag, this means that the current block first has
1343: // to be closed correctly
1344: else if (term_start_index > -1) {
1345: // the text upto the termination tag of the current block has to be extracted
1346: // check if the next termination tag has been double-escaped
1347: leftover_content = removeTrailingDoubleEscape(
1348: content, previous_index, term_start_index);
1349:
1350: // check if a tag is actually open
1351: if (1 == block_ids.size()) {
1352: String class_name = parsed.getClassName();
1353: DocumentPosition doc_position = StringUtils
1354: .getDocumentPosition(content,
1355: term_start_index);
1356: if (term_isblockvalue)
1357: throw new TerminatingUnopenedTagException(
1358: class_name, doc_position,
1359: config_term.mBlockvalueTag
1360: .getText());
1361: else if (term_isblockappend)
1362: throw new TerminatingUnopenedTagException(
1363: class_name, doc_position,
1364: config_term.mBlockappendTag
1365: .getText());
1366: else if (term_iscomment)
1367: throw new TerminatingUnopenedTagException(
1368: class_name, doc_position,
1369: config_term.mCommentTag.getText());
1370: else
1371: throw new TerminatingUnopenedTagException(
1372: class_name, doc_position,
1373: config_term.mBlockTag.getText());
1374: }
1375:
1376: // and added to the content of the current block
1377: blockid = block_ids.peek();
1378: if (blockid != null) {
1379: blocks.get(blockid).append(leftover_content);
1380:
1381: PartType block_type = block_types.peek()
1382: .getType();
1383: // ensure that blockvalue tags are closed with the appropriate termination tag
1384: if (PartType.BLOCKVALUE == block_type) {
1385: if (term_iscomment) {
1386: throw new MismatchedTerminationTagException(
1387: parsed.getClassName(),
1388: StringUtils
1389: .getDocumentPosition(
1390: content,
1391: term_start_index),
1392: blockid,
1393: config_term.mBlockvalueTag
1394: .getText(),
1395: config_term.mCommentTag
1396: .getText());
1397: } else if (term_isblockappend) {
1398: throw new MismatchedTerminationTagException(
1399: parsed.getClassName(),
1400: StringUtils
1401: .getDocumentPosition(
1402: content,
1403: term_start_index),
1404: blockid,
1405: config_term.mBlockvalueTag
1406: .getText(),
1407: config_term.mBlockappendTag
1408: .getText());
1409: } else if (!term_isblockvalue) {
1410: throw new MismatchedTerminationTagException(
1411: parsed.getClassName(),
1412: StringUtils
1413: .getDocumentPosition(
1414: content,
1415: term_start_index),
1416: blockid,
1417: config_term.mBlockvalueTag
1418: .getText(),
1419: config_term.mBlockTag.getText());
1420: }
1421: }
1422: // ensure that blockappend tags are closed with the appropriate termination tag
1423: else if (PartType.BLOCKAPPEND == block_type) {
1424: if (term_iscomment) {
1425: throw new MismatchedTerminationTagException(
1426: parsed.getClassName(),
1427: StringUtils
1428: .getDocumentPosition(
1429: content,
1430: term_start_index),
1431: blockid,
1432: config_term.mBlockappendTag
1433: .getText(),
1434: config_term.mCommentTag
1435: .getText());
1436: } else if (term_isblockvalue) {
1437: throw new MismatchedTerminationTagException(
1438: parsed.getClassName(),
1439: StringUtils
1440: .getDocumentPosition(
1441: content,
1442: term_start_index),
1443: blockid,
1444: config_term.mBlockappendTag
1445: .getText(),
1446: config_term.mBlockvalueTag
1447: .getText());
1448: } else if (!term_isblockappend) {
1449: throw new MismatchedTerminationTagException(
1450: parsed.getClassName(),
1451: StringUtils
1452: .getDocumentPosition(
1453: content,
1454: term_start_index),
1455: blockid,
1456: config_term.mBlockappendTag
1457: .getText(),
1458: config_term.mBlockTag.getText());
1459: }
1460: }
1461: // ensure that the block tags are closed with the appropriate termination tag
1462: else {
1463: if (term_iscomment) {
1464: throw new MismatchedTerminationTagException(
1465: parsed.getClassName(),
1466: StringUtils
1467: .getDocumentPosition(
1468: content,
1469: term_start_index),
1470: blockid, config_term.mBlockTag
1471: .getText(),
1472: config_term.mCommentTag
1473: .getText());
1474: } else if (term_isblockvalue) {
1475: throw new MismatchedTerminationTagException(
1476: parsed.getClassName(),
1477: StringUtils
1478: .getDocumentPosition(
1479: content,
1480: term_start_index),
1481: blockid, config_term.mBlockTag
1482: .getText(),
1483: config_term.mBlockvalueTag
1484: .getText());
1485: } else if (term_isblockappend) {
1486: throw new MismatchedTerminationTagException(
1487: parsed.getClassName(),
1488: StringUtils
1489: .getDocumentPosition(
1490: content,
1491: term_start_index),
1492: blockid, config_term.mBlockTag
1493: .getText(),
1494: config_term.mBlockappendTag
1495: .getText());
1496: }
1497: }
1498: }
1499: // ensure that the comments tags are closed with the appropriate termination tag
1500: else if (!term_iscomment) {
1501: if (term_isblockvalue) {
1502: throw new MismatchedTerminationTagException(
1503: parsed.getClassName(), StringUtils
1504: .getDocumentPosition(
1505: content,
1506: term_start_index),
1507: blockid, config_term.mCommentTag
1508: .getText(),
1509: config_term.mBlockvalueTag
1510: .getText());
1511: } else if (term_isblockappend) {
1512: throw new MismatchedTerminationTagException(
1513: parsed.getClassName(), StringUtils
1514: .getDocumentPosition(
1515: content,
1516: term_start_index),
1517: blockid, config_term.mCommentTag
1518: .getText(),
1519: config_term.mBlockappendTag
1520: .getText());
1521: } else {
1522: throw new MismatchedTerminationTagException(
1523: parsed.getClassName(), StringUtils
1524: .getDocumentPosition(
1525: content,
1526: term_start_index),
1527: blockid, config_term.mCommentTag
1528: .getText(),
1529: config_term.mBlockTag.getText());
1530: }
1531: }
1532:
1533: // the block has been terminated, remove its id from the stack
1534: block_ids.pop();
1535: block_types.pop();
1536: block_configs.pop();
1537:
1538: // continue the search after this tag
1539: previous_index = term_end_index;
1540: }
1541: }
1542: // continue until no start and end tags are found anymore
1543: while (begin_start_index > -1 || term_start_index > -1);
1544:
1545: // check if all tags were correctly closed
1546: if (block_ids.size() > 1) {
1547: throw new MissingTerminationTagsException(parsed
1548: .getClassName(),
1549: StringUtils.getDocumentPosition(content,
1550: content.length()), block_types.peek()
1551: .getText());
1552: }
1553: }
1554:
1555: // append everything that's after the last block to the general content
1556: blocks.get("").append(content.substring(previous_index));
1557:
1558: // iterate over the supported parser configurations to find the ones
1559: // that are unused for value tags
1560: List<Parser.Config> value_configs_list = new ArrayList<Parser.Config>();
1561: for (Parser.Config config : mConfigs) {
1562: if (getEscapedIndex(content, config.mTagStart.getText(), 0,
1563: config.mValueTag, config.mStringDelimiterBegin) != null
1564: || getEscapedIndex(content, config.mTagTerm
1565: .getText(), 0, config.mValueTag,
1566: config.mTagEnd) != null) {
1567: value_configs_list.add(config);
1568: }
1569: }
1570: Parser.Config[] value_configs = null;
1571: if (value_configs_list.size() > 0) {
1572: // make a config array, containing only the used configs
1573: value_configs = new Parser.Config[value_configs_list.size()];
1574: value_configs_list.toArray(value_configs);
1575: }
1576:
1577: Parser.Config[] unescape_configs = getUnescapeConfigs(content);
1578:
1579: // chop the blocks into block parts according to the value tags that are found in each block
1580: for (String block_key : blocks.keySet()) {
1581: parsed.setBlock(block_key, parseBlockParts(value_configs,
1582: unescape_configs, parsed, blocks.get(block_key)
1583: .toString()));
1584: }
1585: }
1586:
1587: private ParsedBlockData parseBlockParts(Parser.Config[] configs,
1588: Parser.Config[] unescapeConfigs, Parsed parsed,
1589: String content) throws TemplateException {
1590: assert parsed != null;
1591: assert content != null;
1592:
1593: ParsedBlockData block_data = new ParsedBlockData();
1594:
1595: int previous_index = 0;
1596: if (configs != null) {
1597: // setup the parser variables
1598: TagMatch match = null;
1599:
1600: int begin_start_index = 0;
1601: boolean begin_isquoted = false;
1602: int term_start_index = 0;
1603: int term_end_index = 0;
1604: int valueid_start_index = 0;
1605: int valueid_end_index = 0;
1606: int begin_term_index = 0;
1607:
1608: int[] begin_start_indices = new int[configs.length];
1609: boolean[] begin_isquoted_flags = new boolean[configs.length];
1610: int[] delimiter_end_indices = new int[configs.length];
1611: int[] term_start_indices = new int[configs.length];
1612: int[] term_end_indices = new int[configs.length];
1613:
1614: String valueid = null;
1615: String valuetag_start = null;
1616:
1617: Parser.Config config_begin = null;
1618: Parser.Config config_term = null;
1619:
1620: // extracts all the parts that make up a block
1621: do {
1622: // iterate over the supported parser configurations to find all
1623: // start tag beginnings
1624: for (int i = 0; i < configs.length; i++) {
1625: match = getEscapedIndex(content,
1626: configs[i].mTagStart.getText(),
1627: previous_index, configs[i].mValueTag,
1628: configs[i].mStringDelimiterBegin);
1629: if (null == match) {
1630: begin_start_indices[i] = -1;
1631: begin_isquoted_flags[i] = false;
1632: delimiter_end_indices[i] = -1;
1633: } else {
1634: begin_start_indices[i] = match.getMatch(0);
1635: begin_isquoted_flags[i] = match
1636: .didFixedPartMatch(1);
1637: delimiter_end_indices[i] = match.getMatch(2);
1638: }
1639: }
1640:
1641: // find the start position of the next value begin tags by comparing
1642: // which is the earliest start tag beginning
1643: config_begin = null;
1644: begin_start_index = -1;
1645: begin_isquoted = false;
1646: for (int i = 0; i < begin_start_indices.length; i++) {
1647: if (begin_start_indices[i] != -1
1648: && (-1 == begin_start_index || begin_start_indices[i] < begin_start_index)) {
1649: begin_start_index = begin_start_indices[i];
1650: begin_isquoted = begin_isquoted_flags[i];
1651: valueid_start_index = delimiter_end_indices[i];
1652: config_begin = configs[i];
1653: }
1654: }
1655:
1656: // check if a begin tag was found
1657: if (-1 != begin_start_index) {
1658: // add the text up to the beginning of this value tag to the list of parts
1659: if (previous_index < begin_start_index) {
1660: String part = unescapePart(unescapeConfigs,
1661: content.substring(previous_index,
1662: begin_start_index));
1663: block_data.addPart(new ParsedBlockText(part));
1664: }
1665:
1666: // get the string delimiter that marks the end of the value id
1667: ConfigPartMatch delimiter_end_match = null;
1668: int valueid_end_offset = 0;
1669: if (begin_isquoted) {
1670: delimiter_end_match = getFirstFoundPartIndex(
1671: content, valueid_start_index,
1672: config_begin.mStringDelimiterEnd);
1673: }
1674:
1675: if (delimiter_end_match != null
1676: && delimiter_end_match.mPart != null) {
1677: valueid_end_index = delimiter_end_match.mIndex;
1678: valueid_end_offset = delimiter_end_match.mPart
1679: .length();
1680: }
1681: // ensure that a name that started out quoted is already terminated with a delimiter
1682: else if (begin_isquoted
1683: && null == delimiter_end_match.mPart) {
1684: throw new AttributeNotEndedException(parsed
1685: .getClassName(), StringUtils
1686: .getDocumentPosition(content,
1687: valueid_start_index),
1688: config_begin.mValueTag.getText(),
1689: "name");
1690: } else {
1691: int long_index = content.indexOf(
1692: config_begin.mTagEnd.getText(),
1693: valueid_start_index);
1694: int short_index = content.indexOf(
1695: config_begin.mTagShortTerm.getText(),
1696: valueid_start_index);
1697: int first_index = getFirstMatch(long_index,
1698: short_index);
1699: valueid_end_offset = 0;
1700: valueid_end_index = backtrackTillFirstNonWhitespace(
1701: content, first_index);
1702: }
1703:
1704: // check if the value id was ended
1705: if (-1 == valueid_end_index) {
1706: throw new AttributeNotEndedException(parsed
1707: .getClassName(), StringUtils
1708: .getDocumentPosition(content,
1709: valueid_start_index),
1710: config_begin.mValueTag.getText(),
1711: "name");
1712: }
1713:
1714: // extract the appearance of the value tag start so that it can be reused later
1715: // when the value hasn't been set in the template
1716: valuetag_start = content.substring(
1717: begin_start_index, valueid_start_index);
1718:
1719: // extract the id of the value and store it
1720: valueid = content.substring(valueid_start_index,
1721: valueid_end_index);
1722:
1723: // ensure that an end delimiter corresponds to a start delimiter
1724: if (!begin_isquoted
1725: && valueid
1726: .endsWith(config_begin.mStringDelimiterEnd
1727: .getText())) {
1728: throw new AttributeWronglyEndedException(
1729: parsed.getClassName(),
1730: StringUtils
1731: .getDocumentPosition(
1732: content,
1733: valueid_end_index
1734: + valueid_end_offset
1735: - config_begin.mStringDelimiterEnd
1736: .length()),
1737: config_begin.mValueTag.getText(),
1738: "name");
1739: }
1740:
1741: // add the value ID to the list of parsed values
1742: parsed.addValue(valueid);
1743:
1744: // get the first tag ending
1745: begin_term_index = content.indexOf(
1746: config_begin.mTagEnd.getText(),
1747: valueid_end_index + valueid_end_offset);
1748:
1749: // ensure that the tag was propertly ended
1750: if (-1 == begin_term_index) {
1751: throw new BeginTagNotEndedException(parsed
1752: .getClassName(), StringUtils
1753: .getDocumentPosition(content,
1754: valueid_end_index
1755: + valueid_end_offset),
1756: config_begin.mValueTag.getText(),
1757: valueid);
1758: }
1759:
1760: // check if it is short value tag
1761: int begin_short_term_index = begin_term_index
1762: - (config_begin.mTagShortTermLength - config_begin.mTagEndLength);
1763: if (config_begin.mTagShortTerm
1764: .equals(content
1765: .substring(
1766: begin_short_term_index,
1767: begin_short_term_index
1768: + config_begin.mTagShortTermLength))) {
1769: // check that the begin tag termination is valid
1770: if (content.substring(
1771: valueid_end_index + valueid_end_offset,
1772: begin_short_term_index).trim().length() > 0) {
1773: throw new BeginTagBadlyTerminatedException(
1774: parsed.getClassName(),
1775: StringUtils
1776: .getDocumentPosition(
1777: content,
1778: valueid_end_index
1779: + valueid_end_offset),
1780: config_begin.mValueTag.getText(),
1781: valueid);
1782: }
1783:
1784: term_start_index = begin_short_term_index;
1785:
1786: // add the value part
1787: block_data
1788: .addPart(new ParsedBlockValue(
1789: valueid,
1790: content
1791: .substring(
1792: begin_start_index,
1793: begin_short_term_index
1794: + config_begin.mTagShortTermLength)));
1795:
1796: // continue the search after this tag
1797: previous_index = term_start_index
1798: + config_begin.mTagShortTermLength;
1799: }
1800: // check if it was a long value tag
1801: else {
1802: // check that the begin tag termination is valid
1803: if (content.substring(
1804: valueid_end_index + valueid_end_offset,
1805: begin_term_index).trim().length() > 0) {
1806: throw new BeginTagBadlyTerminatedException(
1807: parsed.getClassName(),
1808: StringUtils
1809: .getDocumentPosition(
1810: content,
1811: valueid_end_index
1812: + valueid_end_offset),
1813: config_begin.mValueTag.getText(),
1814: valueid);
1815: }
1816:
1817: // iterate over the supported parser configurations all term tag
1818: // beginnings
1819: for (int i = 0; i < configs.length; i++) {
1820: match = getEscapedIndex(content,
1821: configs[i].mTagTerm.getText(),
1822: previous_index,
1823: configs[i].mValueTag,
1824: configs[i].mTagEnd);
1825: if (null == match) {
1826: term_start_indices[i] = -1;
1827: term_end_indices[i] = -1;
1828: } else {
1829: term_start_indices[i] = match
1830: .getMatch(0);
1831: term_end_indices[i] = match.getMatch(2);
1832: }
1833: }
1834:
1835: // find the start position of the next value term tags by comparing
1836: // which is the earliest term tag beginning
1837: config_term = null;
1838: term_start_index = -1;
1839: for (int i = 0; i < term_start_indices.length; i++) {
1840: if (term_start_indices[i] != -1
1841: && (-1 == term_start_index || term_start_indices[i] < term_start_index)) {
1842: term_start_index = term_start_indices[i];
1843: term_end_index = term_end_indices[i];
1844: config_term = configs[i];
1845: }
1846: }
1847:
1848: // the termination tag always has to be found if a begin tag was found
1849: if (-1 == term_start_index) {
1850: throw new TagNotTerminatedException(parsed
1851: .getClassName(), StringUtils
1852: .getDocumentPosition(content,
1853: begin_start_index),
1854: config_begin.mValueTag.getText(),
1855: valueid);
1856: } else {
1857: // iterate over all configuration to check if none introduce a nested value tag
1858: for (Config config_tmp : configs) {
1859: // get the start of the next begin value tag to check for nested value tags
1860: match = getEscapedIndex(
1861: content,
1862: config_tmp.mTagStart.getText(),
1863: valueid_start_index,
1864: config_tmp.mValueTag,
1865: config_tmp.mStringDelimiterBegin);
1866:
1867: // nested value tags are not permitted
1868: if (match != null
1869: && match.getMatch(0) < term_start_index) {
1870: throw new UnsupportedNestedTagException(
1871: parsed.getClassName(),
1872: StringUtils
1873: .getDocumentPosition(
1874: content,
1875: match
1876: .getMatch(0)),
1877: config_tmp.mValueTag
1878: .getText(), valueid);
1879: }
1880: }
1881:
1882: // check if an unopened value tag isn't being closed
1883: if (begin_term_index > term_start_index) {
1884: throw new TerminatingUnopenedTagException(
1885: parsed.getClassName(),
1886: StringUtils
1887: .getDocumentPosition(
1888: content,
1889: term_start_index),
1890: config_term.mValueTag.getText());
1891: }
1892:
1893: // the termination tag comes after the begin tag and the next begin tag after the termination tag
1894: parsed
1895: .setDefaultValue(
1896: valueid,
1897: unescapePart(
1898: unescapeConfigs,
1899: content
1900: .substring(
1901: begin_term_index
1902: + config_begin.mTagEndLength,
1903: term_start_index)));
1904:
1905: // add the value part
1906: block_data
1907: .addPart(new ParsedBlockValue(
1908: valueid,
1909: valuetag_start
1910: + valueid
1911: + config_term.mStringDelimiterEnd
1912: + config_term.mTagShortTerm));
1913:
1914: // continue the search after this tag
1915: previous_index = term_end_index;
1916: }
1917: }
1918: }
1919: }
1920: // continue until no start and end tags are found anymore
1921: while (begin_start_index > -1 && term_start_index > -1);
1922: }
1923:
1924: // append everything that's after the last value as a text block part
1925: if (previous_index < content.length()) {
1926: block_data
1927: .addPart(new ParsedBlockText(unescapePart(
1928: unescapeConfigs, content
1929: .substring(previous_index))));
1930: }
1931:
1932: return block_data;
1933: }
1934:
1935: private FilteredTagsMap filterTags(Pattern[] filters,
1936: Collection<String> tags) {
1937: FilteredTagsMap result = null;
1938:
1939: if (filters != null) {
1940: result = new FilteredTagsMap();
1941:
1942: Matcher filter_matcher = null;
1943: ArrayList<String> captured_groups = null;
1944: String pattern = null;
1945: String[] captured_groups_array = null;
1946:
1947: ArrayList<String> filtered_tags = new ArrayList<String>();
1948:
1949: // iterate over the tag filters
1950: for (Pattern filter_pattern : filters) {
1951: // go over all the tags and try to match them against the current filter
1952: for (String tag : tags) {
1953: // skip over tags that have already been filtered
1954: if (filtered_tags.contains(tag)) {
1955: continue;
1956: }
1957:
1958: // create the filter matcher
1959: filter_matcher = filter_pattern.matcher(tag);
1960:
1961: // if the filter matches, and it returned capturing groups,
1962: // add the returned groups to the filtered tag mapping
1963: while (filter_matcher.find()) {
1964: if (null == captured_groups) {
1965: captured_groups = new ArrayList<String>();
1966: captured_groups.add(tag);
1967: }
1968:
1969: if (filter_matcher.groupCount() > 0) {
1970: // store the captured groups
1971: for (int j = 1; j <= filter_matcher
1972: .groupCount(); j++) {
1973: captured_groups.add(filter_matcher
1974: .group(j));
1975: }
1976: }
1977: }
1978:
1979: if (captured_groups != null) {
1980: pattern = filter_pattern.pattern();
1981:
1982: captured_groups_array = new String[captured_groups
1983: .size()];
1984: captured_groups.toArray(captured_groups_array);
1985:
1986: result.addFilteredTag(pattern,
1987: captured_groups_array);
1988: filtered_tags.add(tag);
1989:
1990: captured_groups_array = null;
1991: captured_groups = null;
1992: }
1993: }
1994: }
1995: }
1996:
1997: return result;
1998: }
1999:
2000: static class TagMatch {
2001: private IntArrayList mMatches = null;
2002: private IntArrayList mFixedPartsMatched = null;
2003:
2004: void addMatch(int match) {
2005: if (null == mMatches) {
2006: mMatches = new IntArrayList();
2007: }
2008:
2009: mMatches.add(match);
2010: }
2011:
2012: int getMatch(int index) {
2013: if (null == mMatches || index >= mMatches.size()) {
2014: return -1;
2015: }
2016:
2017: return mMatches.get(index);
2018: }
2019:
2020: void addFixedPartMatched(boolean match) {
2021: if (null == mFixedPartsMatched) {
2022: mFixedPartsMatched = new IntArrayList();
2023: }
2024:
2025: mFixedPartsMatched.add(match ? 1 : 0);
2026: }
2027:
2028: boolean didFixedPartMatch(int index) {
2029: if (null == mFixedPartsMatched
2030: || index >= mFixedPartsMatched.size()) {
2031: return false;
2032: }
2033:
2034: return 1 == mFixedPartsMatched.get(index);
2035: }
2036:
2037: void clear() {
2038: if (mMatches != null) {
2039: mMatches.clear();
2040: }
2041:
2042: if (mFixedPartsMatched != null) {
2043: mFixedPartsMatched.clear();
2044: }
2045: }
2046: }
2047:
2048: static class ConfigPartMatch {
2049: static final ConfigPartMatch NO_MATCH = new ConfigPartMatch(-1,
2050: null);
2051:
2052: private int mIndex = -1;
2053: private ConfigPart mPart = null;
2054:
2055: ConfigPartMatch(int index, ConfigPart part) {
2056: mIndex = index;
2057: mPart = part;
2058: }
2059:
2060: int length() {
2061: if (null == mPart) {
2062: return 0;
2063: }
2064:
2065: return mPart.length();
2066: }
2067: }
2068:
2069: public static class MandatoryConfigPart extends ConfigPart {
2070: public MandatoryConfigPart(PartType type, String text) {
2071: super (type, text, false);
2072: }
2073: }
2074:
2075: public static class OptionalConfigPart extends ConfigPart {
2076: public OptionalConfigPart(PartType type, String text) {
2077: super (type, text, true);
2078: }
2079: }
2080:
2081: public static class ConfigPart implements CharSequence {
2082: private PartType mType;
2083: private String mText;
2084: private boolean mOptional;
2085:
2086: private ConfigPart(PartType type, String text, boolean optional) {
2087: mType = type;
2088:
2089: if (null == text) {
2090: text = "";
2091: }
2092:
2093: mText = text;
2094: mOptional = optional;
2095: }
2096:
2097: public PartType getType() {
2098: return mType;
2099: }
2100:
2101: public String getText() {
2102: return mText;
2103: }
2104:
2105: public boolean isOptional() {
2106: return mOptional;
2107: }
2108:
2109: public int length() {
2110: return mText.length();
2111: }
2112:
2113: public char charAt(int index) {
2114: return mText.charAt(index);
2115: }
2116:
2117: public CharSequence subSequence(int start, int end) {
2118: return mText.subSequence(start, end);
2119: }
2120:
2121: public String toString() {
2122: return mText;
2123: }
2124:
2125: public boolean equals(Object other) {
2126: if (null == other) {
2127: return false;
2128: }
2129:
2130: if (other == this ) {
2131: return true;
2132: }
2133:
2134: if (other instanceof CharSequence) {
2135: return mText.equals(other);
2136: }
2137:
2138: if (!(other instanceof ConfigPart)) {
2139: return false;
2140: }
2141:
2142: ConfigPart other_part = (ConfigPart) other;
2143: return mType == other_part.mType
2144: && mOptional == other_part.mOptional
2145: && mText.equals(other_part.mText);
2146: }
2147:
2148: public int hashCode() {
2149: return mType.hashCode() * mText.hashCode()
2150: * (mOptional ? 1 : 0);
2151: }
2152: }
2153:
2154: static class Config implements Cloneable {
2155: private ConfigPart mTagStart = null;
2156: private ConfigPart mTagEnd = null;
2157: private ConfigPart mTagTerm = null;
2158: private ConfigPart mTagShortTerm = null;
2159: private ConfigPart mStringDelimiterBegin = null;
2160: private ConfigPart mStringDelimiterEnd = null;
2161: private ConfigPart mValueTag = null;
2162: private ConfigPart mBlockTag = null;
2163: private ConfigPart mBlockvalueTag = null;
2164: private ConfigPart mBlockappendTag = null;
2165: private ConfigPart mIncludeTag = null;
2166: private ConfigPart mCommentTag = null;
2167: private ConfigPart mUnescapeStart = null;
2168:
2169: private Pattern mUnescapePattern = null;
2170:
2171: private int mTagEndLength = -1;
2172: private int mTagTermLength = -1;
2173: private int mTagShortTermLength = -1;
2174:
2175: Config(String tagStart, String tagEnd, String tagTerm,
2176: String tagShortTerm, ConfigPart stringDelimiterBegin,
2177: ConfigPart stringDelimiterEnd, String valueTag,
2178: String blockTag, String blockvalueTag,
2179: String blockappendTag, String includeTag,
2180: String commentTag) {
2181: assert tagStart != null;
2182: assert tagEnd != null;
2183: assert stringDelimiterBegin != null;
2184: assert stringDelimiterEnd != null;
2185: assert tagTerm != null;
2186: assert tagShortTerm != null;
2187: assert valueTag != null;
2188: assert blockTag != null;
2189: assert blockvalueTag != null;
2190: assert blockappendTag != null;
2191: assert includeTag != null;
2192: assert commentTag != null;
2193:
2194: mTagStart = new MandatoryConfigPart(PartType.TAG_START,
2195: tagStart);
2196: mTagEnd = new MandatoryConfigPart(PartType.TAG_END, tagEnd);
2197: mStringDelimiterBegin = stringDelimiterBegin;
2198: mStringDelimiterEnd = stringDelimiterEnd;
2199: mValueTag = new MandatoryConfigPart(PartType.VALUE,
2200: valueTag);
2201: mBlockTag = new MandatoryConfigPart(PartType.BLOCK,
2202: blockTag);
2203: mBlockvalueTag = new MandatoryConfigPart(
2204: PartType.BLOCKVALUE, blockvalueTag);
2205: mBlockappendTag = new MandatoryConfigPart(
2206: PartType.BLOCKAPPEND, blockappendTag);
2207: mIncludeTag = new MandatoryConfigPart(PartType.INCLUDE,
2208: includeTag);
2209: mCommentTag = new MandatoryConfigPart(PartType.COMMENT,
2210: commentTag);
2211: mTagTerm = new MandatoryConfigPart(PartType.TAG_TERM,
2212: tagTerm);
2213: mTagShortTerm = new MandatoryConfigPart(
2214: PartType.TAG_SHORT_TERM, tagShortTerm);
2215: mUnescapeStart = new MandatoryConfigPart(
2216: PartType.UNESCAPE_START, "\\" + tagStart);
2217: mUnescapePattern = Pattern.compile("\\\\((?:\\Q" + tagStart
2218: + "\\E|\\Q" + mTagTerm + "\\E)\\s*(?:"
2219: + mIncludeTag + "|" + mBlockTag + "|"
2220: + mBlockvalueTag + "|" + mBlockappendTag + "|"
2221: + mValueTag + "|" + mCommentTag + "))");
2222:
2223: mTagEndLength = mTagEnd.length();
2224: mTagTermLength = mTagTerm.length();
2225: mTagShortTermLength = mTagShortTerm.length();
2226:
2227: assert mTagTerm != null;
2228: assert mTagShortTerm != null;
2229: assert mStringDelimiterBegin != null;
2230: assert mStringDelimiterEnd != null;
2231: assert mTagEndLength > 0;
2232: assert mTagTermLength > 0;
2233: assert mTagShortTermLength > 0;
2234: }
2235:
2236: public Config clone() {
2237: Config new_parserconfig = null;
2238: try {
2239: new_parserconfig = (Config) super .clone();
2240: } catch (CloneNotSupportedException e) {
2241: new_parserconfig = null;
2242: }
2243:
2244: return new_parserconfig;
2245: }
2246:
2247: public boolean equals(Object object) {
2248: if (object == this ) {
2249: return true;
2250: }
2251:
2252: if (null == object) {
2253: return false;
2254: }
2255:
2256: if (!(object instanceof Config)) {
2257: return false;
2258: }
2259:
2260: Config other_parserconfig = (Config) object;
2261: if (other_parserconfig.mTagStart.equals(this .mTagStart)
2262: && other_parserconfig.mTagEnd.equals(this .mTagEnd)
2263: && other_parserconfig.mTagTerm
2264: .equals(this .mTagTerm)
2265: && other_parserconfig.mTagShortTerm
2266: .equals(this .mTagShortTerm)
2267: && other_parserconfig.mStringDelimiterBegin
2268: .equals(this .mStringDelimiterBegin)
2269: && other_parserconfig.mStringDelimiterEnd
2270: .equals(this .mStringDelimiterEnd)
2271: && other_parserconfig.mValueTag
2272: .equals(this .mValueTag)
2273: && other_parserconfig.mBlockTag
2274: .equals(this .mBlockTag)
2275: && other_parserconfig.mBlockvalueTag
2276: .equals(this .mBlockvalueTag)
2277: && other_parserconfig.mBlockappendTag
2278: .equals(this .mBlockappendTag)
2279: && other_parserconfig.mIncludeTag
2280: .equals(this .mIncludeTag)
2281: && other_parserconfig.mCommentTag
2282: .equals(this .mCommentTag)) {
2283: return true;
2284: } else {
2285: return false;
2286: }
2287: }
2288: }
2289: }
|