You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FormulaParser.java 71KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula;
  16. import java.util.ArrayList;
  17. import java.util.List;
  18. import java.util.Locale;
  19. import java.util.regex.Pattern;
  20. import org.apache.logging.log4j.LogManager;
  21. import org.apache.logging.log4j.Logger;
  22. import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
  23. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  24. import org.apache.poi.ss.SpreadsheetVersion;
  25. import org.apache.poi.ss.formula.constant.ErrorConstant;
  26. import org.apache.poi.ss.formula.function.FunctionMetadata;
  27. import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
  28. import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
  29. import org.apache.poi.ss.formula.ptg.AddPtg;
  30. import org.apache.poi.ss.formula.ptg.Area3DPxg;
  31. import org.apache.poi.ss.formula.ptg.AreaPtg;
  32. import org.apache.poi.ss.formula.ptg.ArrayPtg;
  33. import org.apache.poi.ss.formula.ptg.AttrPtg;
  34. import org.apache.poi.ss.formula.ptg.BoolPtg;
  35. import org.apache.poi.ss.formula.ptg.ConcatPtg;
  36. import org.apache.poi.ss.formula.ptg.DividePtg;
  37. import org.apache.poi.ss.formula.ptg.EqualPtg;
  38. import org.apache.poi.ss.formula.ptg.ErrPtg;
  39. import org.apache.poi.ss.formula.ptg.FuncPtg;
  40. import org.apache.poi.ss.formula.ptg.FuncVarPtg;
  41. import org.apache.poi.ss.formula.ptg.GreaterEqualPtg;
  42. import org.apache.poi.ss.formula.ptg.GreaterThanPtg;
  43. import org.apache.poi.ss.formula.ptg.IntPtg;
  44. import org.apache.poi.ss.formula.ptg.IntersectionPtg;
  45. import org.apache.poi.ss.formula.ptg.LessEqualPtg;
  46. import org.apache.poi.ss.formula.ptg.LessThanPtg;
  47. import org.apache.poi.ss.formula.ptg.MemAreaPtg;
  48. import org.apache.poi.ss.formula.ptg.MemFuncPtg;
  49. import org.apache.poi.ss.formula.ptg.MissingArgPtg;
  50. import org.apache.poi.ss.formula.ptg.MultiplyPtg;
  51. import org.apache.poi.ss.formula.ptg.NamePtg;
  52. import org.apache.poi.ss.formula.ptg.NameXPtg;
  53. import org.apache.poi.ss.formula.ptg.NameXPxg;
  54. import org.apache.poi.ss.formula.ptg.NotEqualPtg;
  55. import org.apache.poi.ss.formula.ptg.NumberPtg;
  56. import org.apache.poi.ss.formula.ptg.OperandPtg;
  57. import org.apache.poi.ss.formula.ptg.OperationPtg;
  58. import org.apache.poi.ss.formula.ptg.ParenthesisPtg;
  59. import org.apache.poi.ss.formula.ptg.PercentPtg;
  60. import org.apache.poi.ss.formula.ptg.PowerPtg;
  61. import org.apache.poi.ss.formula.ptg.Ptg;
  62. import org.apache.poi.ss.formula.ptg.RangePtg;
  63. import org.apache.poi.ss.formula.ptg.RefPtg;
  64. import org.apache.poi.ss.formula.ptg.StringPtg;
  65. import org.apache.poi.ss.formula.ptg.SubtractPtg;
  66. import org.apache.poi.ss.formula.ptg.UnaryMinusPtg;
  67. import org.apache.poi.ss.formula.ptg.UnaryPlusPtg;
  68. import org.apache.poi.ss.formula.ptg.UnionPtg;
  69. import org.apache.poi.ss.formula.ptg.ValueOperatorPtg;
  70. import org.apache.poi.ss.usermodel.FormulaError;
  71. import org.apache.poi.ss.usermodel.Name;
  72. import org.apache.poi.ss.usermodel.Table;
  73. import org.apache.poi.ss.usermodel.Workbook;
  74. import org.apache.poi.ss.util.AreaReference;
  75. import org.apache.poi.ss.util.CellReference;
  76. import org.apache.poi.ss.util.CellReference.NameType;
  77. import org.apache.poi.util.Internal;
  78. /**
  79. * This class parses a formula string into a List of tokens in RPN order.
  80. * Inspired by
  81. * Lets Build a Compiler, by Jack Crenshaw
  82. * BNF for the formula expression is :
  83. * <expression> ::= <term> [<addop> <term>]*
  84. * <term> ::= <factor> [ <mulop> <factor> ]*
  85. * <factor> ::= <number> | (<expression>) | <cellRef> | <function>
  86. * <function> ::= <functionName> ([expression [, expression]*])
  87. * <p>
  88. * For POI internal use only
  89. * <p>
  90. */
  91. @Internal
  92. public final class FormulaParser {
  93. private static final Logger LOGGER = LogManager.getLogger(FormulaParser.class);
  94. private final String _formulaString;
  95. private final int _formulaLength;
  96. /** points at the next character to be read (after the {@link #look} codepoint) */
  97. private int _pointer;
  98. private ParseNode _rootNode;
  99. private static final char TAB = '\t'; // HSSF + XSSF
  100. private static final char CR = '\r'; // Normally just XSSF
  101. private static final char LF = '\n'; // Normally just XSSF
  102. /**
  103. * Lookahead unicode codepoint
  104. * gets value '\0' when the input string is exhausted
  105. */
  106. private int look;
  107. /**
  108. * Tracks whether the run of whitespace preceding "look" could be an
  109. * intersection operator. See GetChar.
  110. */
  111. private boolean _inIntersection;
  112. private final FormulaParsingWorkbook _book;
  113. private final SpreadsheetVersion _ssVersion;
  114. private final int _sheetIndex;
  115. private final int _rowIndex; // 0-based
  116. /**
  117. * Create the formula parser, with the string that is to be
  118. * parsed against the supplied workbook.
  119. * A later call the parse() method to return ptg list in
  120. * rpn order, then call the getRPNPtg() to retrieve the
  121. * parse results.
  122. * This class is recommended only for single threaded use.
  123. *
  124. * If you have a {@link HSSFWorkbook}, and not a
  125. * {@link Workbook}, then use the convenience method on
  126. * {@link HSSFFormulaEvaluator}
  127. */
  128. private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex, int rowIndex) {
  129. _formulaString = formula;
  130. _pointer=0;
  131. _book = book;
  132. _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion();
  133. _formulaLength = _formulaString.length();
  134. _sheetIndex = sheetIndex;
  135. _rowIndex = rowIndex;
  136. }
  137. /**
  138. * Parse a formula into an array of tokens
  139. * Side effect: creates name ({@link Workbook#createName})
  140. * if formula contains unrecognized names (names are likely UDFs)
  141. *
  142. * @param formula the formula to parse
  143. * @param workbook the parent workbook
  144. * @param formulaType the type of the formula
  145. * @param sheetIndex the 0-based index of the sheet this formula belongs to.
  146. * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
  147. * the scope of the name will be ignored and the parser will match names only by name
  148. * @param rowIndex - the related cell's row index in 0-based form (-1 if the formula is not cell related)
  149. * used to handle structured references that have the "#This Row" quantifier.
  150. * Use rowIndex=-1 or {@link #parseStructuredReference(String, FormulaParsingWorkbook, int)} if formula
  151. * does not contain structured references.
  152. *
  153. * @return array of parsed tokens
  154. * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
  155. */
  156. public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, FormulaType formulaType, int sheetIndex, int rowIndex) {
  157. FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
  158. fp.parse();
  159. return fp.getRPNPtg(formulaType);
  160. }
  161. /**
  162. * Parse a formula into an array of tokens
  163. * Side effect: creates name ({@link Workbook#createName})
  164. * if formula contains unrecognized names (names are likely UDFs)
  165. *
  166. * @param formula the formula to parse
  167. * @param workbook the parent workbook
  168. * @param formulaType the type of the formula
  169. * @param sheetIndex the 0-based index of the sheet this formula belongs to.
  170. * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
  171. * the scope of the name will be ignored and the parser will match names only by name
  172. *
  173. * @return array of parsed tokens
  174. * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
  175. */
  176. public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, FormulaType formulaType, int sheetIndex) {
  177. return parse(formula, workbook, formulaType, sheetIndex, -1);
  178. }
  179. /**
  180. * Parse a structured reference. Converts the structured
  181. * reference to the area that represent it.
  182. *
  183. * @param tableText - The structured reference text
  184. * @param workbook - the parent workbook
  185. * @param rowIndex - the 0-based cell's row index ( used to handle "#This Row" quantifiers )
  186. * @return the area that being represented by the structured reference.
  187. */
  188. public static Area3DPxg parseStructuredReference(String tableText, FormulaParsingWorkbook workbook, int rowIndex) {
  189. final int sheetIndex = -1; //don't care?
  190. Ptg[] arr = FormulaParser.parse(tableText, workbook, FormulaType.CELL, sheetIndex, rowIndex);
  191. if (arr.length != 1 || !(arr[0] instanceof Area3DPxg) ) {
  192. throw new IllegalStateException("Illegal structured reference, had length: " + arr.length);
  193. }
  194. return (Area3DPxg) arr[0];
  195. }
  196. /** Read New Character From Input Stream */
  197. private void GetChar() {
  198. // The intersection operator is a space. We track whether the run of
  199. // whitespace preceding "look" counts as an intersection operator.
  200. if (IsWhite(look)) {
  201. if (look == ' ') {
  202. _inIntersection = true;
  203. }
  204. }
  205. else {
  206. _inIntersection = false;
  207. }
  208. // Check to see if we've walked off the end of the string.
  209. if (_pointer > _formulaLength) {
  210. throw new RuntimeException("Parsed past the end of the formula, pos: " + _pointer +
  211. ", length: " + _formulaLength + ", formula: " + _formulaString);
  212. }
  213. if (_pointer < _formulaLength) {
  214. look=_formulaString.codePointAt(_pointer);
  215. } else {
  216. // Just return if so and reset 'look' to something to keep
  217. // SkipWhitespace from spinning
  218. look = (char)0;
  219. _inIntersection = false;
  220. }
  221. _pointer += Character.charCount(look);
  222. }
  223. private void resetPointer(int ptr) {
  224. _pointer = ptr;
  225. if (_pointer <= _formulaLength) {
  226. look=_formulaString.codePointAt(_pointer - Character.charCount(look));
  227. } else {
  228. // Just return if so and reset 'look' to something to keep
  229. // SkipWhitespace from spinning
  230. look = (char)0;
  231. }
  232. }
  233. /** Report What Was Expected */
  234. private RuntimeException expected(String s) {
  235. String msg;
  236. if (look == '=' && _formulaString.substring(0, _pointer-1).trim().length() < 1) {
  237. msg = "The specified formula '" + _formulaString
  238. + "' starts with an equals sign which is not allowed.";
  239. } else {
  240. msg = new StringBuilder("Parse error near char ")
  241. .append(_pointer-1) //this is the codepoint index, not char index, which may be larger if there are multi-byte chars
  242. .append(" '")
  243. .appendCodePoint(look)
  244. .append("'")
  245. .append(" in specified formula '")
  246. .append(_formulaString)
  247. .append("'. Expected ")
  248. .append(s)
  249. .toString();
  250. }
  251. return new FormulaParseException(msg);
  252. }
  253. /** Recognize an Alpha Character */
  254. private static boolean IsAlpha(int c) {
  255. return Character.isLetter(c) || c == '$' || c=='_';
  256. }
  257. /** Recognize a Decimal Digit */
  258. private static boolean IsDigit(int c) {
  259. return Character.isDigit(c);
  260. }
  261. /** Recognize White Space */
  262. private static boolean IsWhite(int c) {
  263. return c ==' ' || c== TAB || c == CR || c == LF;
  264. }
  265. /** Skip Over Leading White Space */
  266. private void SkipWhite() {
  267. while (IsWhite(look)) {
  268. GetChar();
  269. }
  270. }
  271. /**
  272. * Consumes the next input character if it is equal to the one specified otherwise throws an
  273. * unchecked exception. This method does <b>not</b> consume whitespace (before or after the
  274. * matched character).
  275. */
  276. private void Match(int x) {
  277. if (look != x) {
  278. throw expected(new StringBuilder()
  279. .append("'")
  280. .appendCodePoint(x)
  281. .append("'")
  282. .toString());
  283. }
  284. GetChar();
  285. }
  286. /** Get a Number */
  287. private String GetNum() {
  288. StringBuilder value = new StringBuilder();
  289. while (IsDigit(this.look)){
  290. value.appendCodePoint(this.look);
  291. GetChar();
  292. }
  293. return value.length() == 0 ? null : value.toString();
  294. }
  295. private ParseNode parseRangeExpression() {
  296. ParseNode result = parseRangeable();
  297. boolean hasRange = false;
  298. while (look == ':') {
  299. int pos = _pointer;
  300. GetChar();
  301. ParseNode nextPart = parseRangeable();
  302. // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be
  303. // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5"
  304. // Furthermore, Excel doesn't seem to simplify
  305. // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2"
  306. checkValidRangeOperand("LHS", pos, result);
  307. checkValidRangeOperand("RHS", pos, nextPart);
  308. ParseNode[] children = { result, nextPart, };
  309. result = new ParseNode(RangePtg.instance, children);
  310. hasRange = true;
  311. }
  312. if (hasRange) {
  313. return augmentWithMemPtg(result);
  314. }
  315. return result;
  316. }
  317. private static ParseNode augmentWithMemPtg(ParseNode root) {
  318. Ptg memPtg;
  319. if (needsMemFunc(root)) {
  320. memPtg = new MemFuncPtg(root.getEncodedSize());
  321. } else {
  322. memPtg = new MemAreaPtg(root.getEncodedSize());
  323. }
  324. return new ParseNode(memPtg, root);
  325. }
  326. /**
  327. * From OOO doc: "Whenever one operand of the reference subexpression is a function,
  328. * a defined name, a 3D reference, or an external reference (and no error occurs),
  329. * a tMemFunc token is used"
  330. *
  331. */
  332. private static boolean needsMemFunc(ParseNode root) {
  333. Ptg token = root.getToken();
  334. if (token instanceof AbstractFunctionPtg) {
  335. return true;
  336. }
  337. if (token instanceof ExternSheetReferenceToken) { // 3D refs
  338. return true;
  339. }
  340. if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs
  341. return true;
  342. }
  343. if (token instanceof OperationPtg || token instanceof ParenthesisPtg) {
  344. // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc
  345. for(ParseNode child : root.getChildren()) {
  346. if (needsMemFunc(child)) {
  347. return true;
  348. }
  349. }
  350. return false;
  351. }
  352. if (token instanceof OperandPtg) {
  353. return false;
  354. }
  355. return false;
  356. }
  357. /**
  358. * @param currentParsePosition used to format a potential error message
  359. */
  360. private static void checkValidRangeOperand(String sideName, int currentParsePosition, ParseNode pn) {
  361. if (!isValidRangeOperand(pn)) {
  362. throw new FormulaParseException("The " + sideName
  363. + " of the range operator ':' at position "
  364. + currentParsePosition + " is not a proper reference.");
  365. }
  366. }
  367. /**
  368. * @return <code>false</code> if sub-expression represented the specified ParseNode definitely
  369. * cannot appear on either side of the range (':') operator
  370. */
  371. private static boolean isValidRangeOperand(ParseNode a) {
  372. Ptg tkn = a.getToken();
  373. // Note - order is important for these instance-of checks
  374. if (tkn instanceof OperandPtg) {
  375. // notably cell refs and area refs
  376. return true;
  377. }
  378. // next 2 are special cases of OperationPtg
  379. if (tkn instanceof AbstractFunctionPtg) {
  380. AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn;
  381. byte returnClass = afp.getDefaultOperandClass();
  382. return Ptg.CLASS_REF == returnClass;
  383. }
  384. if (tkn instanceof ValueOperatorPtg) {
  385. return false;
  386. }
  387. if (tkn instanceof OperationPtg) {
  388. return true;
  389. }
  390. // one special case of ControlPtg
  391. if (tkn instanceof ParenthesisPtg) {
  392. // parenthesis Ptg should have only one child
  393. return isValidRangeOperand(a.getChildren()[0]);
  394. }
  395. // one special case of ScalarConstantPtg
  396. if (tkn == ErrPtg.REF_INVALID) {
  397. return true;
  398. }
  399. // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
  400. return false;
  401. }
  402. /**
  403. * Parses area refs (things which could be the operand of ':') and simple factors
  404. * Examples
  405. * <pre>
  406. * A$1
  407. * $A$1 : $B1
  408. * A1 ....... C2
  409. * Sheet1 !$A1
  410. * a..b!A1
  411. * 'my sheet'!A1
  412. * .my.sheet!A1
  413. * 'my sheet':'my alt sheet'!A1
  414. * .my.sheet1:.my.sheet2!$B$2
  415. * my.named..range.
  416. * 'my sheet'!my.named.range
  417. * .my.sheet!my.named.range
  418. * foo.bar(123.456, "abc")
  419. * 123.456
  420. * "abc"
  421. * true
  422. * [Foo.xls]!$A$1
  423. * [Foo.xls]'my sheet'!$A$1
  424. * [Foo.xls]!my.named.range
  425. * </pre>
  426. *
  427. */
  428. private ParseNode parseRangeable() {
  429. SkipWhite();
  430. int savePointer = _pointer;
  431. SheetIdentifier sheetIden = parseSheetName();
  432. if (sheetIden == null) {
  433. resetPointer(savePointer);
  434. } else {
  435. SkipWhite();
  436. savePointer = _pointer;
  437. }
  438. SimpleRangePart part1 = parseSimpleRangePart();
  439. if (part1 == null) {
  440. if (sheetIden != null) {
  441. if(look == '#'){ // error ref like MySheet!#REF!
  442. return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
  443. } else {
  444. // Is it a named range?
  445. String name = parseAsName();
  446. if (name.length() == 0) {
  447. throw new FormulaParseException("Cell reference or Named Range "
  448. + "expected after sheet name at index " + _pointer + ".");
  449. }
  450. Ptg nameXPtg = _book.getNameXPtg(name, sheetIden);
  451. if (nameXPtg == null) {
  452. throw new FormulaParseException("Specified name '" + name +
  453. "' for sheet " + sheetIden.asFormulaString() + " not found");
  454. }
  455. return new ParseNode(nameXPtg);
  456. }
  457. }
  458. return parseNonRange(savePointer);
  459. }
  460. boolean whiteAfterPart1 = IsWhite(look);
  461. if (whiteAfterPart1) {
  462. SkipWhite();
  463. }
  464. if (look == ':') {
  465. int colonPos = _pointer;
  466. GetChar();
  467. SkipWhite();
  468. SimpleRangePart part2 = parseSimpleRangePart();
  469. if (part2 != null && !part1.isCompatibleForArea(part2)) {
  470. // second part is not compatible with an area ref e.g. S!A1:S!B2
  471. // where S might be a sheet name (that looks like a column name)
  472. part2 = null;
  473. }
  474. if (part2 == null) {
  475. // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2)
  476. // reset and let caller use explicit range operator
  477. resetPointer(colonPos);
  478. if (!part1.isCell()) {
  479. String prefix = "";
  480. if (sheetIden != null) {
  481. prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!';
  482. }
  483. throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference.");
  484. }
  485. }
  486. return createAreaRefParseNode(sheetIden, part1, part2);
  487. }
  488. if (look == '.') {
  489. GetChar();
  490. int dotCount = 1;
  491. while (look =='.') {
  492. dotCount ++;
  493. GetChar();
  494. }
  495. boolean whiteBeforePart2 = IsWhite(look);
  496. SkipWhite();
  497. SimpleRangePart part2 = parseSimpleRangePart();
  498. String part1And2 = _formulaString.substring(savePointer-1, _pointer-1);
  499. if (part2 == null) {
  500. if (sheetIden != null) {
  501. throw new FormulaParseException("Complete area reference expected after sheet name at index "
  502. + _pointer + ".");
  503. }
  504. return parseNonRange(savePointer);
  505. }
  506. if (whiteAfterPart1 || whiteBeforePart2) {
  507. if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
  508. // "A .. B" not valid syntax for "A:B"
  509. // and there's no other valid expression that fits this grammar
  510. throw new FormulaParseException("Dotted range (full row or column) expression '"
  511. + part1And2 + "' must not contain whitespace.");
  512. }
  513. return createAreaRefParseNode(sheetIden, part1, part2);
  514. }
  515. if (dotCount == 1 && part1.isRow() && part2.isRow()) {
  516. // actually, this is looking more like a number
  517. return parseNonRange(savePointer);
  518. }
  519. if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
  520. if (dotCount != 2) {
  521. throw new FormulaParseException("Dotted range (full row or column) expression '" + part1And2
  522. + "' must have exactly 2 dots.");
  523. }
  524. }
  525. return createAreaRefParseNode(sheetIden, part1, part2);
  526. }
  527. if (part1.isCell() && isValidCellReference(part1.getRep())) {
  528. return createAreaRefParseNode(sheetIden, part1, null);
  529. }
  530. if (sheetIden != null) {
  531. throw new FormulaParseException("Second part of cell reference expected after sheet name at index "
  532. + _pointer + ".");
  533. }
  534. return parseNonRange(savePointer);
  535. }
  536. private static final String specHeaders = "Headers";
  537. private static final String specAll = "All";
  538. private static final String specData = "Data";
  539. private static final String specTotals = "Totals";
  540. private static final String specThisRow = "This Row";
  541. /**
  542. * Parses a structured reference, returns it as area reference.
  543. * Examples:
  544. * <pre>
  545. * Table1[col]
  546. * Table1[[#Totals],[col]]
  547. * Table1[#Totals]
  548. * Table1[#All]
  549. * Table1[#Data]
  550. * Table1[#Headers]
  551. * Table1[#Totals]
  552. * Table1[#This Row]
  553. * Table1[[#All],[col]]
  554. * Table1[[#Headers],[col]]
  555. * Table1[[#Totals],[col]]
  556. * Table1[[#All],[col1]:[col2]]
  557. * Table1[[#Data],[col1]:[col2]]
  558. * Table1[[#Headers],[col1]:[col2]]
  559. * Table1[[#Totals],[col1]:[col2]]
  560. * Table1[[#Headers],[#Data],[col2]]
  561. * Table1[[#This Row], [col1]]
  562. * Table1[ [col1]:[col2] ]
  563. * </pre>
  564. * @param tableName
  565. * @return Area Reference for the given table
  566. */
  567. private ParseNode parseStructuredReference(String tableName) {
  568. if ( ! (_ssVersion.equals(SpreadsheetVersion.EXCEL2007)) ) {
  569. throw new FormulaParseException("Structured references work only on XSSF (Excel 2007+)!");
  570. }
  571. Table tbl = _book.getTable(tableName);
  572. if (tbl == null) {
  573. throw new FormulaParseException("Illegal table name: '" + tableName + "'");
  574. }
  575. String sheetName = tbl.getSheetName();
  576. int startCol = tbl.getStartColIndex();
  577. int endCol = tbl.getEndColIndex();
  578. int startRow = tbl.getStartRowIndex();
  579. int endRow = tbl.getEndRowIndex();
  580. // Do NOT return before done reading all the structured reference tokens from the input stream.
  581. // Throwing exceptions is okay.
  582. int savePtr0 = _pointer;
  583. GetChar();
  584. boolean isTotalsSpec = false;
  585. boolean isThisRowSpec = false;
  586. boolean isDataSpec = false;
  587. boolean isHeadersSpec = false;
  588. boolean isAllSpec = false;
  589. int nSpecQuantifiers = 0; // The number of special quantifiers
  590. while (true) {
  591. int savePtr1 = _pointer;
  592. String specName = parseAsSpecialQuantifier();
  593. if (specName == null) {
  594. resetPointer(savePtr1);
  595. break;
  596. }
  597. switch (specName) {
  598. case specAll:
  599. isAllSpec = true;
  600. break;
  601. case specData:
  602. isDataSpec = true;
  603. break;
  604. case specHeaders:
  605. isHeadersSpec = true;
  606. break;
  607. case specThisRow:
  608. isThisRowSpec = true;
  609. break;
  610. case specTotals:
  611. isTotalsSpec = true;
  612. break;
  613. default:
  614. throw new FormulaParseException("Unknown special quantifier " + specName);
  615. }
  616. nSpecQuantifiers++;
  617. if (look == ','){
  618. GetChar();
  619. } else {
  620. break;
  621. }
  622. }
  623. boolean isThisRow = false;
  624. SkipWhite();
  625. if (look == '@') {
  626. isThisRow = true;
  627. GetChar();
  628. }
  629. // parse column quantifier
  630. String startColumnName;
  631. String endColumnName = null;
  632. int nColQuantifiers = 0;
  633. int savePtr1 = _pointer;
  634. startColumnName = parseAsColumnQuantifier();
  635. if (startColumnName == null) {
  636. resetPointer(savePtr1);
  637. } else {
  638. nColQuantifiers++;
  639. if (look == ','){
  640. throw new FormulaParseException("The formula "+ _formulaString + " is illegal: you should not use ',' with column quantifiers");
  641. } else if (look == ':') {
  642. GetChar();
  643. endColumnName = parseAsColumnQuantifier();
  644. nColQuantifiers++;
  645. if (endColumnName == null) {
  646. throw new FormulaParseException("The formula "+ _formulaString + " is illegal: the string after ':' must be column quantifier");
  647. }
  648. }
  649. }
  650. if(nColQuantifiers == 0 && nSpecQuantifiers == 0){
  651. resetPointer(savePtr0);
  652. savePtr0 = _pointer;
  653. startColumnName = parseAsColumnQuantifier();
  654. if (startColumnName != null) {
  655. nColQuantifiers++;
  656. } else {
  657. resetPointer(savePtr0);
  658. String name = parseAsSpecialQuantifier();
  659. if (name!=null) {
  660. switch (name) {
  661. case specAll:
  662. isAllSpec = true;
  663. break;
  664. case specData:
  665. isDataSpec = true;
  666. break;
  667. case specHeaders:
  668. isHeadersSpec = true;
  669. break;
  670. case specThisRow:
  671. isThisRowSpec = true;
  672. break;
  673. case specTotals:
  674. isTotalsSpec = true;
  675. break;
  676. default:
  677. throw new FormulaParseException("Unknown special quantifier " + name);
  678. }
  679. nSpecQuantifiers++;
  680. } else {
  681. throw new FormulaParseException("The formula "+ _formulaString + " is illegal");
  682. }
  683. }
  684. } else {
  685. Match(']');
  686. }
  687. // Done reading from input stream
  688. // Ok to return now
  689. if (isTotalsSpec && tbl.getTotalsRowCount() == 0) {
  690. return new ParseNode(ErrPtg.REF_INVALID);
  691. }
  692. if ((isThisRow || isThisRowSpec) && (_rowIndex < startRow || endRow < _rowIndex)) {
  693. // structured reference is trying to reference a row above or below the table with [#This Row] or [@]
  694. if (_rowIndex >= 0) {
  695. return new ParseNode(ErrPtg.VALUE_INVALID);
  696. } else {
  697. throw new FormulaParseException(
  698. "Formula contained [#This Row] or [@] structured reference but this row < 0. " +
  699. "Row index must be specified for row-referencing structured references.");
  700. }
  701. }
  702. int actualStartRow = startRow;
  703. int actualEndRow = endRow;
  704. int actualStartCol = startCol;
  705. int actualEndCol = endCol;
  706. if (nSpecQuantifiers > 0) {
  707. //Selecting rows
  708. if (nSpecQuantifiers == 1 && isAllSpec) {
  709. //do nothing
  710. } else if (isDataSpec && isHeadersSpec) {
  711. if (tbl.getTotalsRowCount() > 0) {
  712. actualEndRow = endRow - 1;
  713. }
  714. } else if (isDataSpec && isTotalsSpec) {
  715. actualStartRow = startRow + 1;
  716. } else if (nSpecQuantifiers == 1 && isDataSpec) {
  717. actualStartRow = startRow + 1;
  718. if (tbl.getTotalsRowCount() > 0) {
  719. actualEndRow = endRow - 1;
  720. }
  721. } else if (nSpecQuantifiers == 1 && isHeadersSpec) {
  722. actualEndRow = actualStartRow;
  723. } else if (nSpecQuantifiers == 1 && isTotalsSpec) {
  724. actualStartRow = actualEndRow;
  725. } else if ((nSpecQuantifiers == 1 && isThisRowSpec) || isThisRow) {
  726. actualStartRow = _rowIndex; //The rowNum is 0 based
  727. actualEndRow = _rowIndex;
  728. } else {
  729. throw new FormulaParseException("The formula "+ _formulaString + " is illegal");
  730. }
  731. } else {
  732. if (isThisRow) { // there is a @
  733. actualStartRow = _rowIndex; //The rowNum is 0 based
  734. actualEndRow = _rowIndex;
  735. } else { // Really no special quantifiers
  736. actualStartRow++;
  737. if (tbl.getTotalsRowCount() > 0) actualEndRow--;
  738. }
  739. }
  740. //Selecting cols
  741. if (nColQuantifiers == 2) {
  742. if (startColumnName == null || endColumnName == null) {
  743. throw new IllegalStateException("Fatal error");
  744. }
  745. int startIdx = tbl.findColumnIndex(startColumnName);
  746. int endIdx = tbl.findColumnIndex(endColumnName);
  747. if (startIdx == -1 || endIdx == -1) {
  748. throw new FormulaParseException("One of the columns "+ startColumnName +", "+ endColumnName +" doesn't exist in table "+ tbl.getName());
  749. }
  750. actualStartCol = startCol+ startIdx;
  751. actualEndCol = startCol + endIdx;
  752. } else if (nColQuantifiers == 1 && !isThisRow) {
  753. if (startColumnName == null) {
  754. throw new IllegalStateException("Fatal error");
  755. }
  756. int idx = tbl.findColumnIndex(startColumnName);
  757. if (idx == -1) {
  758. throw new FormulaParseException("The column "+ startColumnName + " doesn't exist in table "+ tbl.getName());
  759. }
  760. actualStartCol = startCol + idx;
  761. actualEndCol = actualStartCol;
  762. }
  763. CellReference topLeft = new CellReference(actualStartRow, actualStartCol);
  764. CellReference bottomRight = new CellReference(actualEndRow, actualEndCol);
  765. SheetIdentifier sheetIden = new SheetIdentifier( null, new NameIdentifier(sheetName, true));
  766. Ptg ptg = _book.get3DReferencePtg(new AreaReference(topLeft, bottomRight, _ssVersion), sheetIden);
  767. return new ParseNode(ptg);
  768. }
  769. /**
  770. * Tries to parse the next as column - can contain whitespace
  771. * Caller should save pointer.
  772. */
  773. private String parseAsColumnQuantifier() {
  774. if ( look != '[') {
  775. return null;
  776. }
  777. GetChar();
  778. if (look == '#') {
  779. return null;
  780. }
  781. if (look == '@') {
  782. GetChar();
  783. }
  784. StringBuilder name = new StringBuilder();
  785. while (look!=']') {
  786. name.appendCodePoint(look);
  787. GetChar();
  788. }
  789. Match(']');
  790. return name.toString();
  791. }
  792. /**
  793. * Tries to parse the next as special quantifier
  794. * Caller should save pointer.
  795. */
  796. private String parseAsSpecialQuantifier(){
  797. if ( look != '[') {
  798. return null;
  799. }
  800. GetChar();
  801. if( look != '#') {
  802. return null;
  803. }
  804. GetChar();
  805. String name = parseAsName();
  806. if ( name.equals("This")) {
  807. name = name + ' ' + parseAsName();
  808. }
  809. Match(']');
  810. return name;
  811. }
  812. /**
  813. * Parses simple factors that are not primitive ranges or range components
  814. * i.e. '!', ':'(and equiv '...') do not appear
  815. * Examples
  816. * <pre>
  817. * my.named...range.
  818. * foo.bar(123.456, "abc")
  819. * 123.456
  820. * "abc"
  821. * true
  822. * </pre>
  823. */
  824. private ParseNode parseNonRange(int savePointer) {
  825. resetPointer(savePointer);
  826. if (Character.isDigit(look)) {
  827. return new ParseNode(parseNumber());
  828. }
  829. if (look == '"') {
  830. return new ParseNode(new StringPtg(parseStringLiteral()));
  831. }
  832. // from now on we can only be dealing with non-quoted identifiers
  833. // which will either be named ranges or functions
  834. String name = parseAsName();
  835. if (look == '(') {
  836. return function(name);
  837. }
  838. if(look == '['){
  839. return parseStructuredReference(name);
  840. }
  841. if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
  842. return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
  843. }
  844. if (_book == null) {
  845. // Only test cases omit the book (expecting it not to be needed)
  846. throw new IllegalStateException("Need book to evaluate name '" + name + "'");
  847. }
  848. EvaluationName evalName = _book.getName(name, _sheetIndex);
  849. if (evalName == null) {
  850. throw new FormulaParseException("Specified named range '"
  851. + name + "' does not exist in the current workbook.");
  852. }
  853. if (evalName.isRange()) {
  854. return new ParseNode(evalName.createPtg());
  855. }
  856. // TODO - what about NameX ?
  857. throw new FormulaParseException("Specified name '"
  858. + name + "' is not a range as expected.");
  859. }
  860. private String parseAsName() {
  861. StringBuilder sb = new StringBuilder();
  862. // defined names may begin with a letter or underscore or backslash
  863. if (!Character.isLetter(look) && look != '_' && look != '\\') {
  864. throw expected("number, string, defined name, or data table");
  865. }
  866. while (isValidDefinedNameChar(look)) {
  867. sb.appendCodePoint(look);
  868. GetChar();
  869. }
  870. SkipWhite();
  871. return sb.toString();
  872. }
  873. /**
  874. * @param ch unicode codepoint
  875. * @return <code>true</code> if the specified character may be used in a defined name
  876. */
  877. private static boolean isValidDefinedNameChar(int ch) {
  878. if (Character.isLetterOrDigit(ch)) {
  879. return true;
  880. }
  881. // the sheet naming rules are vague on whether unicode characters are allowed
  882. // assume they're allowed.
  883. if (ch > 128) {
  884. return true;
  885. }
  886. switch (ch) {
  887. case '.':
  888. case '_':
  889. case '?':
  890. case '\\': // of all things
  891. return true;
  892. }
  893. // includes special non-name control characters like ! $ : , ( ) [ ] and space
  894. return false;
  895. }
  896. /**
  897. *
  898. * @param sheetIden may be <code>null</code>
  899. * @param part1
  900. * @param part2 may be <code>null</code>
  901. */
  902. private ParseNode createAreaRefParseNode(SheetIdentifier sheetIden, SimpleRangePart part1,
  903. SimpleRangePart part2) throws FormulaParseException {
  904. Ptg ptg;
  905. if (part2 == null) {
  906. CellReference cr = part1.getCellReference();
  907. if (sheetIden == null) {
  908. ptg = new RefPtg(cr);
  909. } else {
  910. ptg = _book.get3DReferencePtg(cr, sheetIden);
  911. }
  912. } else {
  913. AreaReference areaRef = createAreaRef(part1, part2);
  914. if (sheetIden == null) {
  915. ptg = new AreaPtg(areaRef);
  916. } else {
  917. ptg = _book.get3DReferencePtg(areaRef, sheetIden);
  918. }
  919. }
  920. return new ParseNode(ptg);
  921. }
  922. private AreaReference createAreaRef(SimpleRangePart part1, SimpleRangePart part2) {
  923. if (!part1.isCompatibleForArea(part2)) {
  924. throw new FormulaParseException("has incompatible parts: '"
  925. + part1.getRep() + "' and '" + part2.getRep() + "'.");
  926. }
  927. if (part1.isRow()) {
  928. return AreaReference.getWholeRow(_ssVersion, part1.getRep(), part2.getRep());
  929. }
  930. if (part1.isColumn()) {
  931. return AreaReference.getWholeColumn(_ssVersion, part1.getRep(), part2.getRep());
  932. }
  933. return new AreaReference(part1.getCellReference(), part2.getCellReference(), _ssVersion);
  934. }
  935. /**
  936. * Matches a zero or one letter-runs followed by zero or one digit-runs.
  937. * Either or both runs man optionally be prefixed with a single '$'.
  938. * (copied+modified from {@link CellReference#CELL_REF_PATTERN})
  939. */
  940. private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Za-z]+)?(\\$?[0-9]+)?");
  941. /**
  942. * Parses out a potential LHS or RHS of a ':' intended to produce a plain AreaRef. Normally these are
  943. * proper cell references but they could also be row or column refs like "$AC" or "10"
  944. * @return <code>null</code> (and leaves {@link #_pointer} unchanged if a proper range part does not parse out
  945. */
  946. private SimpleRangePart parseSimpleRangePart() {
  947. int ptr = _pointer-1; // TODO avoid StringIndexOutOfBounds
  948. boolean hasDigits = false;
  949. boolean hasLetters = false;
  950. while (ptr < _formulaLength) {
  951. char ch = _formulaString.charAt(ptr);
  952. if (Character.isDigit(ch)) {
  953. hasDigits = true;
  954. } else if (Character.isLetter(ch)) {
  955. hasLetters = true;
  956. } else if (ch =='$' || ch =='_') {
  957. //
  958. } else {
  959. break;
  960. }
  961. ptr++;
  962. }
  963. if (ptr <= _pointer-1) {
  964. return null;
  965. }
  966. String rep = _formulaString.substring(_pointer-1, ptr);
  967. if (!CELL_REF_PATTERN.matcher(rep).matches()) {
  968. return null;
  969. }
  970. // Check range bounds against grid max
  971. if (hasLetters && hasDigits) {
  972. if (!isValidCellReference(rep)) {
  973. return null;
  974. }
  975. } else if (hasLetters) {
  976. if (!CellReference.isColumnWithinRange(rep.replace("$", ""), _ssVersion)) {
  977. return null;
  978. }
  979. } else if (hasDigits) {
  980. int i;
  981. try {
  982. i = Integer.parseInt(rep.replace("$", ""));
  983. } catch (NumberFormatException e) {
  984. return null;
  985. }
  986. if (i<1 || i>_ssVersion.getMaxRows()) {
  987. return null;
  988. }
  989. } else {
  990. // just dollars ? can this happen?
  991. return null;
  992. }
  993. resetPointer(ptr+1); // stepping forward
  994. return new SimpleRangePart(rep, hasLetters, hasDigits);
  995. }
  996. /**
  997. * A1, $A1, A$1, $A$1, A, 1
  998. */
  999. private static final class SimpleRangePart {
  1000. private enum Type {
  1001. CELL, ROW, COLUMN;
  1002. public static Type get(boolean hasLetters, boolean hasDigits) {
  1003. if (hasLetters) {
  1004. return hasDigits ? CELL : COLUMN;
  1005. }
  1006. if (!hasDigits) {
  1007. throw new IllegalArgumentException("must have either letters or numbers");
  1008. }
  1009. return ROW;
  1010. }
  1011. }
  1012. private final Type _type;
  1013. private final String _rep;
  1014. public SimpleRangePart(String rep, boolean hasLetters, boolean hasNumbers) {
  1015. _rep = rep;
  1016. _type = Type.get(hasLetters, hasNumbers);
  1017. }
  1018. public boolean isCell() {
  1019. return _type == Type.CELL;
  1020. }
  1021. public boolean isRowOrColumn() {
  1022. return _type != Type.CELL;
  1023. }
  1024. public CellReference getCellReference() {
  1025. if (_type != Type.CELL) {
  1026. throw new IllegalStateException("Not applicable to this type");
  1027. }
  1028. return new CellReference(_rep);
  1029. }
  1030. public boolean isColumn() {
  1031. return _type == Type.COLUMN;
  1032. }
  1033. public boolean isRow() {
  1034. return _type == Type.ROW;
  1035. }
  1036. public String getRep() {
  1037. return _rep;
  1038. }
  1039. /**
  1040. * @return <code>true</code> if the two range parts can be combined in an
  1041. * {@link AreaPtg} ( Note - the explicit range operator (:) may still be valid
  1042. * when this method returns <code>false</code> )
  1043. */
  1044. public boolean isCompatibleForArea(SimpleRangePart part2) {
  1045. return _type == part2._type;
  1046. }
  1047. @Override
  1048. public String toString() {
  1049. return getClass().getName() + " [" + _rep + "]";
  1050. }
  1051. }
  1052. private String getBookName() {
  1053. StringBuilder sb = new StringBuilder();
  1054. GetChar();
  1055. while (look != ']') {
  1056. sb.appendCodePoint(look);
  1057. GetChar();
  1058. }
  1059. GetChar();
  1060. return sb.toString();
  1061. }
  1062. /**
  1063. * Note - caller should reset {@link #_pointer} upon <code>null</code> result
  1064. * @return The sheet name as an identifier <code>null</code> if '!' is not found in the right place
  1065. */
  1066. private SheetIdentifier parseSheetName() {
  1067. String bookName;
  1068. if (look == '[') {
  1069. bookName = getBookName();
  1070. } else {
  1071. bookName = null;
  1072. }
  1073. if (look == '\'') {
  1074. Match('\'');
  1075. if (look == '[')
  1076. bookName = getBookName();
  1077. StringBuilder sb = new StringBuilder();
  1078. boolean done = look == '\'';
  1079. while(!done) {
  1080. sb.appendCodePoint(look);
  1081. GetChar();
  1082. if(look == '\'')
  1083. {
  1084. Match('\'');
  1085. done = look != '\'';
  1086. }
  1087. }
  1088. NameIdentifier iden = new NameIdentifier(sb.toString(), true);
  1089. // quoted identifier - can't concatenate anything more
  1090. SkipWhite();
  1091. if (look == '!') {
  1092. GetChar();
  1093. return new SheetIdentifier(bookName, iden);
  1094. }
  1095. // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
  1096. if (look == ':') {
  1097. return parseSheetRange(bookName, iden);
  1098. }
  1099. return null;
  1100. }
  1101. // unquoted sheet names must start with underscore or a letter
  1102. if (look =='_' || Character.isLetter(look)) {
  1103. StringBuilder sb = new StringBuilder();
  1104. // can concatenate idens with dots
  1105. while (isUnquotedSheetNameChar(look)) {
  1106. sb.appendCodePoint(look);
  1107. GetChar();
  1108. }
  1109. NameIdentifier iden = new NameIdentifier(sb.toString(), false);
  1110. SkipWhite();
  1111. if (look == '!') {
  1112. GetChar();
  1113. return new SheetIdentifier(bookName, iden);
  1114. }
  1115. // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
  1116. if (look == ':') {
  1117. return parseSheetRange(bookName, iden);
  1118. }
  1119. return null;
  1120. }
  1121. if (look == '!' && bookName != null) {
  1122. // Raw book reference, without a sheet
  1123. GetChar();
  1124. return new SheetIdentifier(bookName, null);
  1125. }
  1126. return null;
  1127. }
  1128. /**
  1129. * If we have something that looks like [book]Sheet1: or
  1130. * Sheet1, see if it's actually a range eg Sheet1:Sheet2!
  1131. */
  1132. private SheetIdentifier parseSheetRange(String bookname, NameIdentifier sheet1Name) {
  1133. GetChar();
  1134. SheetIdentifier sheet2 = parseSheetName();
  1135. if (sheet2 != null) {
  1136. return new SheetRangeIdentifier(bookname, sheet1Name, sheet2.getSheetIdentifier());
  1137. }
  1138. return null;
  1139. }
  1140. /**
  1141. * very similar to {@link SheetNameFormatter#isSpecialChar(char)}
  1142. * @param ch unicode codepoint
  1143. */
  1144. private static boolean isUnquotedSheetNameChar(int ch) {
  1145. if(Character.isLetterOrDigit(ch)) {
  1146. return true;
  1147. }
  1148. // the sheet naming rules are vague on whether unicode characters are allowed
  1149. // assume they're allowed.
  1150. if (ch > 128) {
  1151. return true;
  1152. }
  1153. switch(ch) {
  1154. case '.': // dot is OK
  1155. case '_': // underscore is OK
  1156. return true;
  1157. }
  1158. return false;
  1159. }
  1160. /**
  1161. * @return <code>true</code> if the specified name is a valid cell reference
  1162. */
  1163. private boolean isValidCellReference(String str) {
  1164. //check range bounds against grid max
  1165. boolean result = CellReference.classifyCellReference(str, _ssVersion) == NameType.CELL;
  1166. if(result){
  1167. /*
  1168. * Check if the argument is a function. Certain names can be either a cell reference or a function name
  1169. * depending on the contenxt. Compare the following examples in Excel 2007:
  1170. * (a) LOG10(100) + 1
  1171. * (b) LOG10 + 1
  1172. * In (a) LOG10 is a name of a built-in function. In (b) LOG10 is a cell reference
  1173. */
  1174. boolean isFunc = FunctionMetadataRegistry.getFunctionByName(str.toUpperCase(Locale.ROOT)) != null;
  1175. if(isFunc){
  1176. int savePointer = _pointer;
  1177. resetPointer(_pointer + str.length());
  1178. SkipWhite();
  1179. // open bracket indicates that the argument is a function,
  1180. // the returning value should be false, i.e. "not a valid cell reference"
  1181. result = look != '(';
  1182. resetPointer(savePointer);
  1183. }
  1184. }
  1185. return result;
  1186. }
  1187. /**
  1188. * Note - Excel function names are 'case aware but not case sensitive'. This method may end
  1189. * up creating a defined name record in the workbook if the specified name is not an internal
  1190. * Excel function, and has not been encountered before.
  1191. *
  1192. * Side effect: creates workbook name if name is not recognized (name is probably a UDF)
  1193. *
  1194. * @param name case preserved function name (as it was entered/appeared in the formula).
  1195. */
  1196. private ParseNode function(String name) {
  1197. Ptg nameToken = null;
  1198. if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
  1199. // user defined function
  1200. // in the token tree, the name is more or less the first argument
  1201. if (_book == null) {
  1202. // Only test cases omit the book (expecting it not to be needed)
  1203. throw new IllegalStateException("Need book to evaluate name '" + name + "'");
  1204. }
  1205. // Check to see if name is a named range in the workbook
  1206. EvaluationName hName = _book.getName(name, _sheetIndex);
  1207. if (hName != null) {
  1208. if (!hName.isFunctionName()) {
  1209. throw new FormulaParseException("Attempt to use name '" + name
  1210. + "' as a function, but defined name in workbook does not refer to a function");
  1211. }
  1212. // calls to user-defined functions within the workbook
  1213. // get a Name token which points to a defined name record
  1214. nameToken = hName.createPtg();
  1215. } else {
  1216. // Check if name is an external names table
  1217. nameToken = _book.getNameXPtg(name, null);
  1218. if (nameToken == null) {
  1219. // name is not an internal or external name
  1220. LOGGER.atWarn().log("FormulaParser.function: Name '{}' is completely unknown in the current workbook.", name);
  1221. // name is probably the name of an unregistered User-Defined Function
  1222. switch (_book.getSpreadsheetVersion()) {
  1223. case EXCEL97:
  1224. // HSSFWorkbooks require a name to be added to Workbook defined names table
  1225. addName(name);
  1226. hName = _book.getName(name, _sheetIndex);
  1227. nameToken = hName.createPtg();
  1228. break;
  1229. case EXCEL2007:
  1230. // XSSFWorkbooks store formula names as strings.
  1231. nameToken = new NameXPxg(name);
  1232. break;
  1233. default:
  1234. throw new IllegalStateException("Unexpected spreadsheet version: " + _book.getSpreadsheetVersion().name());
  1235. }
  1236. }
  1237. }
  1238. }
  1239. Match('(');
  1240. ParseNode[] args = Arguments();
  1241. Match(')');
  1242. return getFunction(name, nameToken, args);
  1243. }
  1244. /**
  1245. * Adds a name (named range or user defined function) to underlying workbook's names table
  1246. */
  1247. private void addName(String functionName) {
  1248. final Name name = _book.createName();
  1249. name.setFunction(true);
  1250. name.setNameName(functionName);
  1251. name.setSheetIndex(_sheetIndex);
  1252. }
  1253. /**
  1254. * Generates the variable function ptg for the formula.
  1255. * <p>
  1256. * For IF Formulas, additional PTGs are added to the tokens
  1257. * @param name a {@link NamePtg} or {@link NameXPtg} or <code>null</code>
  1258. * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
  1259. */
  1260. private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
  1261. FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT));
  1262. int numArgs = args.length;
  1263. if(fm == null) {
  1264. if (namePtg == null) {
  1265. throw new IllegalStateException("NamePtg must be supplied for external functions");
  1266. }
  1267. // must be external function
  1268. ParseNode[] allArgs = new ParseNode[numArgs+1];
  1269. allArgs[0] = new ParseNode(namePtg);
  1270. System.arraycopy(args, 0, allArgs, 1, numArgs);
  1271. return new ParseNode(FuncVarPtg.create(name, numArgs+1), allArgs);
  1272. }
  1273. if (namePtg != null) {
  1274. throw new IllegalStateException("NamePtg no applicable to internal functions");
  1275. }
  1276. boolean isVarArgs = !fm.hasFixedArgsLength();
  1277. int funcIx = fm.getIndex();
  1278. if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) {
  1279. // Excel encodes the sum of a single argument as tAttrSum
  1280. // POI does the same for consistency, but this is not critical
  1281. return new ParseNode(AttrPtg.getSumSingle(), args);
  1282. // The code below would encode tFuncVar(SUM) which seems to do no harm
  1283. }
  1284. validateNumArgs(args.length, fm);
  1285. AbstractFunctionPtg retval;
  1286. if(isVarArgs) {
  1287. retval = FuncVarPtg.create(name, numArgs);
  1288. } else {
  1289. retval = FuncPtg.create(funcIx);
  1290. }
  1291. return new ParseNode(retval, args);
  1292. }
  1293. private void validateNumArgs(int numArgs, FunctionMetadata fm) {
  1294. if(numArgs < fm.getMinParams()) {
  1295. String msg = "Too few arguments to function '" + fm.getName() + "'. ";
  1296. if(fm.hasFixedArgsLength()) {
  1297. msg += "Expected " + fm.getMinParams();
  1298. } else {
  1299. msg += "At least " + fm.getMinParams() + " were expected";
  1300. }
  1301. msg += " but got " + numArgs + ".";
  1302. throw new FormulaParseException(msg);
  1303. }
  1304. //the maximum number of arguments depends on the Excel version
  1305. int maxArgs;
  1306. if (fm.hasUnlimitedVarags()) {
  1307. if(_book != null) {
  1308. maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs();
  1309. } else {
  1310. //_book can be omitted by test cases
  1311. maxArgs = fm.getMaxParams(); // just use BIFF8
  1312. }
  1313. } else {
  1314. maxArgs = fm.getMaxParams();
  1315. }
  1316. if(numArgs > maxArgs) {
  1317. String msg = "Too many arguments to function '" + fm.getName() + "'. ";
  1318. if(fm.hasFixedArgsLength()) {
  1319. msg += "Expected " + maxArgs;
  1320. } else {
  1321. msg += "At most " + maxArgs + " were expected";
  1322. }
  1323. msg += " but got " + numArgs + ".";
  1324. throw new FormulaParseException(msg);
  1325. }
  1326. }
  1327. /**
  1328. * @param ch unicode codepoint
  1329. *
  1330. */
  1331. private static boolean isArgumentDelimiter(int ch) {
  1332. return ch == ',' || ch == ')';
  1333. }
  1334. /** get arguments to a function */
  1335. private ParseNode[] Arguments() {
  1336. //average 2 args per function
  1337. List<ParseNode> temp = new ArrayList<>(2);
  1338. SkipWhite();
  1339. if(look == ')') {
  1340. return ParseNode.EMPTY_ARRAY;
  1341. }
  1342. boolean missedPrevArg = true;
  1343. while (true) {
  1344. SkipWhite();
  1345. if (isArgumentDelimiter(look)) {
  1346. if (missedPrevArg) {
  1347. temp.add(new ParseNode(MissingArgPtg.instance));
  1348. }
  1349. if (look == ')') {
  1350. break;
  1351. }
  1352. Match(',');
  1353. missedPrevArg = true;
  1354. continue;
  1355. }
  1356. temp.add(intersectionExpression());
  1357. missedPrevArg = false;
  1358. SkipWhite();
  1359. if (!isArgumentDelimiter(look)) {
  1360. throw expected("',' or ')'");
  1361. }
  1362. }
  1363. ParseNode[] result = new ParseNode[temp.size()];
  1364. temp.toArray(result);
  1365. return result;
  1366. }
  1367. /** Parse and Translate a Math Factor */
  1368. private ParseNode powerFactor() {
  1369. ParseNode result = percentFactor();
  1370. while(true) {
  1371. SkipWhite();
  1372. if(look != '^') {
  1373. return result;
  1374. }
  1375. Match('^');
  1376. ParseNode other = percentFactor();
  1377. result = new ParseNode(PowerPtg.instance, result, other);
  1378. }
  1379. }
  1380. private ParseNode percentFactor() {
  1381. ParseNode result = parseSimpleFactor();
  1382. while(true) {
  1383. SkipWhite();
  1384. if(look != '%') {
  1385. return result;
  1386. }
  1387. Match('%');
  1388. result = new ParseNode(PercentPtg.instance, result);
  1389. }
  1390. }
  1391. /**
  1392. * factors (without ^ or % )
  1393. */
  1394. private ParseNode parseSimpleFactor() {
  1395. SkipWhite();
  1396. switch(look) {
  1397. case '#':
  1398. return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
  1399. case '-':
  1400. Match('-');
  1401. return parseUnary(false);
  1402. case '+':
  1403. Match('+');
  1404. return parseUnary(true);
  1405. case '(':
  1406. Match('(');
  1407. ParseNode inside = unionExpression();
  1408. Match(')');
  1409. return new ParseNode(ParenthesisPtg.instance, inside);
  1410. case '"':
  1411. return new ParseNode(new StringPtg(parseStringLiteral()));
  1412. case '{':
  1413. Match('{');
  1414. ParseNode arrayNode = parseArray();
  1415. Match('}');
  1416. return arrayNode;
  1417. }
  1418. // named ranges and tables can start with underscore or backslash
  1419. // see https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4d0f13ac-53b7-422e-afd2-abd7ff379c64?ui=en-US&rs=en-US&ad=US#bmsyntax_rules_for_names
  1420. if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '[' || look == '_' || look == '\\' ) {
  1421. return parseRangeExpression();
  1422. }
  1423. if (look == '.') {
  1424. return new ParseNode(parseNumber());
  1425. }
  1426. throw expected("cell ref or constant literal");
  1427. }
  1428. private ParseNode parseUnary(boolean isPlus) {
  1429. boolean numberFollows = IsDigit(look) || look=='.';
  1430. ParseNode factor = powerFactor();
  1431. if (numberFollows) {
  1432. // + or - directly next to a number is parsed with the number
  1433. Ptg token = factor.getToken();
  1434. if (token instanceof NumberPtg) {
  1435. if (isPlus) {
  1436. return factor;
  1437. }
  1438. token = new NumberPtg(-((NumberPtg)token).getValue());
  1439. return new ParseNode(token);
  1440. }
  1441. if (token instanceof IntPtg) {
  1442. if (isPlus) {
  1443. return factor;
  1444. }
  1445. int intVal = ((IntPtg)token).getValue();
  1446. // note - cannot use IntPtg for negatives
  1447. token = new NumberPtg(-intVal);
  1448. return new ParseNode(token);
  1449. }
  1450. }
  1451. return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor);
  1452. }
  1453. private ParseNode parseArray() {
  1454. List<Object[]> rowsData = new ArrayList<>();
  1455. while(true) {
  1456. Object[] singleRowData = parseArrayRow();
  1457. rowsData.add(singleRowData);
  1458. if (look == '}') {
  1459. break;
  1460. }
  1461. if (look != ';') {
  1462. throw expected("'}' or ';'");
  1463. }
  1464. Match(';');
  1465. }
  1466. int nRows = rowsData.size();
  1467. Object[][] values2d = new Object[nRows][];
  1468. rowsData.toArray(values2d);
  1469. int nColumns = values2d[0].length;
  1470. checkRowLengths(values2d, nColumns);
  1471. return new ParseNode(new ArrayPtg(values2d));
  1472. }
  1473. private void checkRowLengths(Object[][] values2d, int nColumns) {
  1474. for (int i = 0; i < values2d.length; i++) {
  1475. int rowLen = values2d[i].length;
  1476. if (rowLen != nColumns) {
  1477. throw new FormulaParseException("Array row " + i + " has length " + rowLen
  1478. + " but row 0 has length " + nColumns);
  1479. }
  1480. }
  1481. }
  1482. private Object[] parseArrayRow() {
  1483. List<Object> temp = new ArrayList<>();
  1484. while (true) {
  1485. temp.add(parseArrayItem());
  1486. SkipWhite();
  1487. switch(look) {
  1488. case '}':
  1489. case ';':
  1490. break;
  1491. case ',':
  1492. Match(',');
  1493. continue;
  1494. default:
  1495. throw expected("'}' or ','");
  1496. }
  1497. break;
  1498. }
  1499. Object[] result = new Object[temp.size()];
  1500. temp.toArray(result);
  1501. return result;
  1502. }
  1503. private Object parseArrayItem() {
  1504. SkipWhite();
  1505. switch(look) {
  1506. case '"': return parseStringLiteral();
  1507. case '#': return ErrorConstant.valueOf(parseErrorLiteral());
  1508. case 'F': case 'f':
  1509. case 'T': case 't':
  1510. return parseBooleanLiteral();
  1511. case '-':
  1512. Match('-');
  1513. SkipWhite();
  1514. return convertArrayNumber(parseNumber(), false);
  1515. }
  1516. // else assume number
  1517. return convertArrayNumber(parseNumber(), true);
  1518. }
  1519. private Boolean parseBooleanLiteral() {
  1520. String iden = parseUnquotedIdentifier();
  1521. if ("TRUE".equalsIgnoreCase(iden)) {
  1522. return Boolean.TRUE;
  1523. }
  1524. if ("FALSE".equalsIgnoreCase(iden)) {
  1525. return Boolean.FALSE;
  1526. }
  1527. throw expected("'TRUE' or 'FALSE'");
  1528. }
  1529. private static Double convertArrayNumber(Ptg ptg, boolean isPositive) {
  1530. double value;
  1531. if (ptg instanceof IntPtg) {
  1532. value = ((IntPtg)ptg).getValue();
  1533. } else if (ptg instanceof NumberPtg) {
  1534. value = ((NumberPtg)ptg).getValue();
  1535. } else {
  1536. throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
  1537. }
  1538. if (!isPositive) {
  1539. value = -value;
  1540. }
  1541. return Double.valueOf(value);
  1542. }
  1543. private Ptg parseNumber() {
  1544. String number2 = null;
  1545. String exponent = null;
  1546. String number1 = GetNum();
  1547. if (look == '.') {
  1548. GetChar();
  1549. number2 = GetNum();
  1550. }
  1551. if (look == 'E') {
  1552. GetChar();
  1553. String sign = "";
  1554. if (look == '+') {
  1555. GetChar();
  1556. } else if (look == '-') {
  1557. GetChar();
  1558. sign = "-";
  1559. }
  1560. String number = GetNum();
  1561. if (number == null) {
  1562. throw expected("Integer");
  1563. }
  1564. exponent = sign + number;
  1565. }
  1566. if (number1 == null && number2 == null) {
  1567. throw expected("Integer");
  1568. }
  1569. return getNumberPtgFromString(number1, number2, exponent);
  1570. }
  1571. private int parseErrorLiteral() {
  1572. Match('#');
  1573. String part1 = parseUnquotedIdentifier();
  1574. if (part1 == null) {
  1575. throw expected("remainder of error constant literal");
  1576. }
  1577. part1 = part1.toUpperCase(Locale.ROOT);
  1578. switch(part1.charAt(0)) {
  1579. case 'V': {
  1580. FormulaError fe = FormulaError.VALUE;
  1581. if(part1.equals(fe.name())) {
  1582. Match('!');
  1583. return fe.getCode();
  1584. }
  1585. throw expected(fe.getString());
  1586. }
  1587. case 'R': {
  1588. FormulaError fe = FormulaError.REF;
  1589. if(part1.equals(fe.name())) {
  1590. Match('!');
  1591. return fe.getCode();
  1592. }
  1593. throw expected(fe.getString());
  1594. }
  1595. case 'D': {
  1596. FormulaError fe = FormulaError.DIV0;
  1597. if(part1.equals("DIV")) {
  1598. Match('/');
  1599. Match('0');
  1600. Match('!');
  1601. return fe.getCode();
  1602. }
  1603. throw expected(fe.getString());
  1604. }
  1605. case 'N': {
  1606. FormulaError fe = FormulaError.NAME;
  1607. if(part1.equals(fe.name())) {
  1608. // only one that ends in '?'
  1609. Match('?');
  1610. return fe.getCode();
  1611. }
  1612. fe = FormulaError.NUM;
  1613. if(part1.equals(fe.name())) {
  1614. Match('!');
  1615. return fe.getCode();
  1616. }
  1617. fe = FormulaError.NULL;
  1618. if(part1.equals(fe.name())) {
  1619. Match('!');
  1620. return fe.getCode();
  1621. }
  1622. fe = FormulaError.NA;
  1623. if(part1.equals("N")) {
  1624. Match('/');
  1625. if(look != 'A' && look != 'a') {
  1626. throw expected(fe.getString());
  1627. }
  1628. Match(look);
  1629. // Note - no '!' or '?' suffix
  1630. return fe.getCode();
  1631. }
  1632. throw expected("#NAME?, #NUM!, #NULL! or #N/A");
  1633. }
  1634. }
  1635. throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
  1636. }
  1637. private String parseUnquotedIdentifier() {
  1638. if (look == '\'') {
  1639. throw expected("unquoted identifier");
  1640. }
  1641. StringBuilder sb = new StringBuilder();
  1642. while (Character.isLetterOrDigit(look) || look == '.') {
  1643. sb.appendCodePoint(look);
  1644. GetChar();
  1645. }
  1646. if (sb.length() < 1) {
  1647. return null;
  1648. }
  1649. return sb.toString();
  1650. }
  1651. /**
  1652. * Get a PTG for an integer from its string representation.
  1653. * return Int or Number Ptg based on size of input
  1654. */
  1655. private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
  1656. StringBuilder number = new StringBuilder();
  1657. if (number2 == null) {
  1658. number.append(number1);
  1659. if (exponent != null) {
  1660. number.append('E');
  1661. number.append(exponent);
  1662. }
  1663. String numberStr = number.toString();
  1664. int intVal;
  1665. try {
  1666. intVal = Integer.parseInt(numberStr);
  1667. } catch (NumberFormatException e) {
  1668. return new NumberPtg(numberStr);
  1669. }
  1670. if (IntPtg.isInRange(intVal)) {
  1671. return new IntPtg(intVal);
  1672. }
  1673. return new NumberPtg(numberStr);
  1674. }
  1675. if (number1 != null) {
  1676. number.append(number1);
  1677. }
  1678. number.append('.');
  1679. number.append(number2);
  1680. if (exponent != null) {
  1681. number.append('E');
  1682. number.append(exponent);
  1683. }
  1684. return new NumberPtg(number.toString());
  1685. }
  1686. private String parseStringLiteral() {
  1687. Match('"');
  1688. StringBuilder token = new StringBuilder();
  1689. while (true) {
  1690. if (look == '"') {
  1691. GetChar();
  1692. if (look != '"') {
  1693. break;
  1694. }
  1695. }
  1696. token.appendCodePoint(look);
  1697. GetChar();
  1698. }
  1699. return token.toString();
  1700. }
  1701. /** Parse and Translate a Math Term */
  1702. private ParseNode Term() {
  1703. ParseNode result = powerFactor();
  1704. while(true) {
  1705. SkipWhite();
  1706. Ptg operator;
  1707. switch(look) {
  1708. case '*':
  1709. Match('*');
  1710. operator = MultiplyPtg.instance;
  1711. break;
  1712. case '/':
  1713. Match('/');
  1714. operator = DividePtg.instance;
  1715. break;
  1716. default:
  1717. return result; // finished with Term
  1718. }
  1719. ParseNode other = powerFactor();
  1720. result = new ParseNode(operator, result, other);
  1721. }
  1722. }
  1723. private ParseNode unionExpression() {
  1724. ParseNode result = intersectionExpression();
  1725. boolean hasUnions = false;
  1726. while (true) {
  1727. SkipWhite();
  1728. if (look == ',') {
  1729. GetChar();
  1730. hasUnions = true;
  1731. ParseNode other = intersectionExpression();
  1732. result = new ParseNode(UnionPtg.instance, result, other);
  1733. continue;
  1734. }
  1735. if (hasUnions) {
  1736. return augmentWithMemPtg(result);
  1737. }
  1738. return result;
  1739. }
  1740. }
  1741. private ParseNode intersectionExpression() {
  1742. ParseNode result = comparisonExpression();
  1743. boolean hasIntersections = false;
  1744. while (true) {
  1745. SkipWhite();
  1746. if (_inIntersection) {
  1747. int savePointer = _pointer;
  1748. // Don't getChar() as the space has already been eaten and recorded by SkipWhite().
  1749. try {
  1750. ParseNode other = comparisonExpression();
  1751. result = new ParseNode(IntersectionPtg.instance, result, other);
  1752. hasIntersections = true;
  1753. continue;
  1754. } catch (FormulaParseException e) {
  1755. // if parsing for intersection fails we assume that we actually had an arbitrary
  1756. // whitespace and thus should simply skip this whitespace
  1757. resetPointer(savePointer);
  1758. }
  1759. }
  1760. if (hasIntersections) {
  1761. return augmentWithMemPtg(result);
  1762. }
  1763. return result;
  1764. }
  1765. }
  1766. private ParseNode comparisonExpression() {
  1767. ParseNode result = concatExpression();
  1768. while (true) {
  1769. SkipWhite();
  1770. switch(look) {
  1771. case '=':
  1772. case '>':
  1773. case '<':
  1774. Ptg comparisonToken = getComparisonToken();
  1775. ParseNode other = concatExpression();
  1776. result = new ParseNode(comparisonToken, result, other);
  1777. continue;
  1778. }
  1779. return result; // finished with predicate expression
  1780. }
  1781. }
  1782. private Ptg getComparisonToken() {
  1783. if(look == '=') {
  1784. Match(look);
  1785. return EqualPtg.instance;
  1786. }
  1787. boolean isGreater = look == '>';
  1788. Match(look);
  1789. if(isGreater) {
  1790. if(look == '=') {
  1791. Match('=');
  1792. return GreaterEqualPtg.instance;
  1793. }
  1794. return GreaterThanPtg.instance;
  1795. }
  1796. switch(look) {
  1797. case '=':
  1798. Match('=');
  1799. return LessEqualPtg.instance;
  1800. case '>':
  1801. Match('>');
  1802. return NotEqualPtg.instance;
  1803. }
  1804. return LessThanPtg.instance;
  1805. }
  1806. private ParseNode concatExpression() {
  1807. ParseNode result = additiveExpression();
  1808. while (true) {
  1809. SkipWhite();
  1810. if(look != '&') {
  1811. break; // finished with concat expression
  1812. }
  1813. Match('&');
  1814. ParseNode other = additiveExpression();
  1815. result = new ParseNode(ConcatPtg.instance, result, other);
  1816. }
  1817. return result;
  1818. }
  1819. /** Parse and Translate an Expression */
  1820. private ParseNode additiveExpression() {
  1821. ParseNode result = Term();
  1822. while (true) {
  1823. SkipWhite();
  1824. Ptg operator;
  1825. switch(look) {
  1826. case '+':
  1827. Match('+');
  1828. operator = AddPtg.instance;
  1829. break;
  1830. case '-':
  1831. Match('-');
  1832. operator = SubtractPtg.instance;
  1833. break;
  1834. default:
  1835. return result; // finished with additive expression
  1836. }
  1837. ParseNode other = Term();
  1838. result = new ParseNode(operator, result, other);
  1839. }
  1840. }
  1841. //{--------------------------------------------------------------}
  1842. //{ Parse and Translate an Assignment Statement }
  1843. /*
  1844. procedure Assignment;
  1845. var Name: string[8];
  1846. begin
  1847. Name := GetName;
  1848. Match('=');
  1849. Expression;
  1850. end;
  1851. **/
  1852. /**
  1853. * API call to execute the parsing of the formula
  1854. *
  1855. */
  1856. private void parse() {
  1857. _pointer=0;
  1858. GetChar();
  1859. _rootNode = unionExpression();
  1860. if(_pointer <= _formulaLength) {
  1861. String msg = "Unused input [" + _formulaString.substring(_pointer-1)
  1862. + "] after attempting to parse the formula [" + _formulaString + "]";
  1863. throw new FormulaParseException(msg);
  1864. }
  1865. }
  1866. private Ptg[] getRPNPtg(FormulaType formulaType) {
  1867. OperandClassTransformer oct = new OperandClassTransformer(formulaType);
  1868. // RVA is for 'operand class': 'reference', 'value', 'array'
  1869. oct.transformFormula(_rootNode);
  1870. return ParseNode.toTokenArray(_rootNode);
  1871. }
  1872. }