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.

OperandClassTransformer.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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 org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
  17. import org.apache.poi.ss.formula.ptg.AttrPtg;
  18. import org.apache.poi.ss.formula.ptg.ControlPtg;
  19. import org.apache.poi.ss.formula.ptg.FuncVarPtg;
  20. import org.apache.poi.ss.formula.ptg.IntersectionPtg;
  21. import org.apache.poi.ss.formula.ptg.MemAreaPtg;
  22. import org.apache.poi.ss.formula.ptg.MemFuncPtg;
  23. import org.apache.poi.ss.formula.ptg.Ptg;
  24. import org.apache.poi.ss.formula.ptg.RangePtg;
  25. import org.apache.poi.ss.formula.ptg.UnionPtg;
  26. import org.apache.poi.ss.formula.ptg.ValueOperatorPtg;
  27. /**
  28. * This class performs 'operand class' transformation. Non-base tokens are classified into three
  29. * operand classes:
  30. * <ul>
  31. * <li>reference</li>
  32. * <li>value</li>
  33. * <li>array</li>
  34. * </ul>
  35. * <p/>
  36. *
  37. * The final operand class chosen for each token depends on the formula type and the token's place
  38. * in the formula. If POI gets the operand class wrong, Excel <em>may</em> interpret the formula
  39. * incorrectly. This condition is typically manifested as a formula cell that displays as '#VALUE!',
  40. * but resolves correctly when the user presses F2, enter.<p/>
  41. *
  42. * The logic implemented here was partially inspired by the description in
  43. * "OpenOffice.org's Documentation of the Microsoft Excel File Format". The model presented there
  44. * seems to be inconsistent with observed Excel behaviour (These differences have not been fully
  45. * investigated). The implementation in this class has been heavily modified in order to satisfy
  46. * concrete examples of how Excel performs the same logic (see TestRVA).<p/>
  47. *
  48. * Hopefully, as additional important test cases are identified and added to the test suite,
  49. * patterns might become more obvious in this code and allow for simplification.
  50. *
  51. * @author Josh Micich
  52. */
  53. final class OperandClassTransformer {
  54. private final int _formulaType;
  55. public OperandClassTransformer(int formulaType) {
  56. _formulaType = formulaType;
  57. }
  58. /**
  59. * Traverses the supplied formula parse tree, calling <tt>Ptg.setClass()</tt> for each non-base
  60. * token to set its operand class.
  61. */
  62. public void transformFormula(ParseNode rootNode) {
  63. byte rootNodeOperandClass;
  64. switch (_formulaType) {
  65. case FormulaType.CELL:
  66. rootNodeOperandClass = Ptg.CLASS_VALUE;
  67. break;
  68. case FormulaType.ARRAY:
  69. rootNodeOperandClass = Ptg.CLASS_ARRAY;
  70. break;
  71. case FormulaType.NAMEDRANGE:
  72. case FormulaType.DATAVALIDATION_LIST:
  73. rootNodeOperandClass = Ptg.CLASS_REF;
  74. break;
  75. default:
  76. throw new RuntimeException("Incomplete code - formula type ("
  77. + _formulaType + ") not supported yet");
  78. }
  79. transformNode(rootNode, rootNodeOperandClass, false);
  80. }
  81. /**
  82. * @param callerForceArrayFlag <code>true</code> if one of the current node's parents is a
  83. * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on
  84. * the function return value).
  85. */
  86. private void transformNode(ParseNode node, byte desiredOperandClass,
  87. boolean callerForceArrayFlag) {
  88. Ptg token = node.getToken();
  89. ParseNode[] children = node.getChildren();
  90. boolean isSimpleValueFunc = isSimpleValueFunction(token);
  91. if (isSimpleValueFunc) {
  92. boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY;
  93. for (int i = 0; i < children.length; i++) {
  94. transformNode(children[i], desiredOperandClass, localForceArray);
  95. }
  96. setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag);
  97. return;
  98. }
  99. if (isSingleArgSum(token)) {
  100. // Need to process the argument of SUM with transformFunctionNode below
  101. // so make a dummy FuncVarPtg for that call.
  102. token = FuncVarPtg.SUM;
  103. // Note - the tAttrSum token (node.getToken()) is a base
  104. // token so does not need to have its operand class set
  105. }
  106. if (token instanceof ValueOperatorPtg || token instanceof ControlPtg
  107. || token instanceof MemFuncPtg
  108. || token instanceof MemAreaPtg
  109. || token instanceof UnionPtg
  110. || token instanceof IntersectionPtg) {
  111. // Value Operator Ptgs and Control are base tokens, so token will be unchanged
  112. // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag
  113. // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
  114. // All direct operands of value operators that are initially 'R' type will
  115. // be converted to 'V' type.
  116. byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass;
  117. for (int i = 0; i < children.length; i++) {
  118. transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag);
  119. }
  120. return;
  121. }
  122. if (token instanceof AbstractFunctionPtg) {
  123. transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag);
  124. return;
  125. }
  126. if (children.length > 0) {
  127. if (token == RangePtg.instance) {
  128. // TODO is any token transformation required under the various ref operators?
  129. return;
  130. }
  131. throw new IllegalStateException("Node should not have any children");
  132. }
  133. if (token.isBaseToken()) {
  134. // nothing to do
  135. return;
  136. }
  137. token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag));
  138. }
  139. private static boolean isSingleArgSum(Ptg token) {
  140. if (token instanceof AttrPtg) {
  141. AttrPtg attrPtg = (AttrPtg) token;
  142. return attrPtg.isSum();
  143. }
  144. return false;
  145. }
  146. private static boolean isSimpleValueFunction(Ptg token) {
  147. if (token instanceof AbstractFunctionPtg) {
  148. AbstractFunctionPtg aptg = (AbstractFunctionPtg) token;
  149. if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) {
  150. return false;
  151. }
  152. int numberOfOperands = aptg.getNumberOfOperands();
  153. for (int i=numberOfOperands-1; i>=0; i--) {
  154. if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) {
  155. return false;
  156. }
  157. }
  158. return true;
  159. }
  160. return false;
  161. }
  162. private byte transformClass(byte currentOperandClass, byte desiredOperandClass,
  163. boolean callerForceArrayFlag) {
  164. switch (desiredOperandClass) {
  165. case Ptg.CLASS_VALUE:
  166. if (!callerForceArrayFlag) {
  167. return Ptg.CLASS_VALUE;
  168. }
  169. // else fall through
  170. case Ptg.CLASS_ARRAY:
  171. return Ptg.CLASS_ARRAY;
  172. case Ptg.CLASS_REF:
  173. if (!callerForceArrayFlag) {
  174. return currentOperandClass;
  175. }
  176. return Ptg.CLASS_REF;
  177. }
  178. throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")");
  179. }
  180. private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children,
  181. byte desiredOperandClass, boolean callerForceArrayFlag) {
  182. boolean localForceArrayFlag;
  183. byte defaultReturnOperandClass = afp.getDefaultOperandClass();
  184. if (callerForceArrayFlag) {
  185. switch (defaultReturnOperandClass) {
  186. case Ptg.CLASS_REF:
  187. if (desiredOperandClass == Ptg.CLASS_REF) {
  188. afp.setClass(Ptg.CLASS_REF);
  189. } else {
  190. afp.setClass(Ptg.CLASS_ARRAY);
  191. }
  192. localForceArrayFlag = false;
  193. break;
  194. case Ptg.CLASS_ARRAY:
  195. afp.setClass(Ptg.CLASS_ARRAY);
  196. localForceArrayFlag = false;
  197. break;
  198. case Ptg.CLASS_VALUE:
  199. afp.setClass(Ptg.CLASS_ARRAY);
  200. localForceArrayFlag = true;
  201. break;
  202. default:
  203. throw new IllegalStateException("Unexpected operand class ("
  204. + defaultReturnOperandClass + ")");
  205. }
  206. } else {
  207. if (defaultReturnOperandClass == desiredOperandClass) {
  208. localForceArrayFlag = false;
  209. // an alternative would have been to for non-base Ptgs to set their operand class
  210. // from their default, but this would require the call in many subclasses because
  211. // the default OC is not known until the end of the constructor
  212. afp.setClass(defaultReturnOperandClass);
  213. } else {
  214. switch (desiredOperandClass) {
  215. case Ptg.CLASS_VALUE:
  216. // always OK to set functions to return 'value'
  217. afp.setClass(Ptg.CLASS_VALUE);
  218. localForceArrayFlag = false;
  219. break;
  220. case Ptg.CLASS_ARRAY:
  221. switch (defaultReturnOperandClass) {
  222. case Ptg.CLASS_REF:
  223. afp.setClass(Ptg.CLASS_REF);
  224. // afp.setClass(Ptg.CLASS_ARRAY);
  225. break;
  226. case Ptg.CLASS_VALUE:
  227. afp.setClass(Ptg.CLASS_ARRAY);
  228. break;
  229. default:
  230. throw new IllegalStateException("Unexpected operand class ("
  231. + defaultReturnOperandClass + ")");
  232. }
  233. localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE);
  234. break;
  235. case Ptg.CLASS_REF:
  236. switch (defaultReturnOperandClass) {
  237. case Ptg.CLASS_ARRAY:
  238. afp.setClass(Ptg.CLASS_ARRAY);
  239. break;
  240. case Ptg.CLASS_VALUE:
  241. afp.setClass(Ptg.CLASS_VALUE);
  242. break;
  243. default:
  244. throw new IllegalStateException("Unexpected operand class ("
  245. + defaultReturnOperandClass + ")");
  246. }
  247. localForceArrayFlag = false;
  248. break;
  249. default:
  250. throw new IllegalStateException("Unexpected operand class ("
  251. + desiredOperandClass + ")");
  252. }
  253. }
  254. }
  255. for (int i = 0; i < children.length; i++) {
  256. ParseNode child = children[i];
  257. byte paramOperandClass = afp.getParameterClass(i);
  258. transformNode(child, paramOperandClass, localForceArrayFlag);
  259. }
  260. }
  261. private void setSimpleValueFuncClass(AbstractFunctionPtg afp,
  262. byte desiredOperandClass, boolean callerForceArrayFlag) {
  263. if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) {
  264. afp.setClass(Ptg.CLASS_ARRAY);
  265. } else {
  266. afp.setClass(Ptg.CLASS_VALUE);
  267. }
  268. }
  269. }