/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008 jOpenDocument, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU
* General Public License Version 3 only ("GPL").
* You may not use this file except in compliance with the License.
* You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
* See the License for the specific language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*
*/
package org.jopendocument.dom.template.statements;
import org.jopendocument.dom.template.TemplateException;
import org.jopendocument.dom.template.engine.DataModel;
import org.jopendocument.dom.template.engine.Processor;
import org.jopendocument.util.ExceptionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.xpath.XPath;
/**
* ForEach tag: repeats a document element for each item in a collection. <br>
* Attributes:
* <ul>
* <li><b>element </b> (required): the document element to which this tag applies, e.g. "table-row"
* </li>
* <li><b>items </b> (required): expression evaluating to a Collection to be iterated</li>
* <li><b>var </b> (required): variable name where the current iteration item is stored</li>
* <li><b>alternate </b> (optional): number of document elements to be alternated during iteration,
* tipically used for alternating row background colors with a value of "2"</li>
* </ul>
*/
public class ForEach extends BaseStatement {
private static Logger logger = Logger.getLogger(ForEach.class.getName());
public ForEach() {
super("forEach");
}
@SuppressWarnings("unchecked")
public void prepare(Element script, Element command) throws TemplateException {
String elementName = command.getAttributeValue("element");
if (elementName == null)
throw new TemplateException("missing required attribute for forEach tag: element");
String itemsExpression = command.getAttributeValue("items");
if (itemsExpression == null)
throw new TemplateException("missing required attribute for forEach tag: items");
String varName = command.getAttributeValue("var");
if (varName == null)
throw new TemplateException("missing required attribute for forEach tag: var");
// alternate
final int alternate;
String alternateString = command.getAttributeValue("alternate");
if (alternateString == null) {
alternate = 1;
} else {
try {
alternate = Integer.parseInt(alternateString);
} catch (Throwable t) {
throw new TemplateException("invalid alternate attribute for forEach tag: " + alternateString);
}
}
// multiple
final int multiple;
final String multipleString = command.getAttributeValue("multiple");
if (multipleString == null) {
multiple = 0;
} else {
try {
multiple = Integer.parseInt(multipleString);
} catch (Throwable t) {
throw new TemplateException("invalid alternate attribute for forEach tag: " + multipleString);
}
}
if (alternate > 1 && multiple > 0) {
throw new TemplateException("both alternate and multiple have been specified");
}
Element element = getAncestorByName(script, elementName);
if (element == null) {
throw new TemplateException("no such element enclosing forEach: " + elementName + " for expression: " + itemsExpression);
}
Element parent = element.getParentElement();
// Element forEachTag = parent.addElement(getQName("forEach")).addAttribute("items",
// itemsExpression).addAttribute("var", varName);
final Element forEachTag = getElement("forEach").setAttribute("items", itemsExpression).setAttribute("var", varName);
final int siblingsToAdd;
if (multiple > 0) {
forEachTag.setAttribute("multiple", "yes");
siblingsToAdd = multiple - 1;
} else {
siblingsToAdd = alternate - 1;
}
int index = parent.indexOf(element);
List<Element> targets = new ArrayList<Element>();
targets.add(element);
if (siblingsToAdd > 0) {
List siblings;
try {
final XPath xpath = XPath.newInstance("following-sibling::*");
siblings = xpath.selectNodes(element);
} catch (JDOMException e) {
throw ExceptionUtils.createExn(TemplateException.class, "xpath error", e);
}
if (siblingsToAdd > siblings.size())
throw new TemplateException("alternate or multiple is greater (" + (siblingsToAdd + 1) + ") than actual table size (" + siblings.size() + ").");
for (int i = 0; i < siblingsToAdd; i++) {
Element sibling = (Element) siblings.get(i);
targets.add(sibling);
sibling.detach();
}
}
element.detach();
parent.getContent().add(index, forEachTag);
for (int i = 0; i < targets.size(); i++) {
forEachTag.addContent(targets.get(i));
}
script.detach();
}
@SuppressWarnings("unchecked")
public void execute(Processor processor, Element tag, DataModel model) throws TemplateException {
String varName = tag.getAttributeValue("var");
String itemsExpression = tag.getAttributeValue("items");
final boolean multiple = "yes".equals(tag.getAttributeValue("multiple"));
Object value = model.eval(itemsExpression);
if (value == null) {
logger.info("forEach items: null expression: " + itemsExpression);
return;
}
final Collection items;
if (value instanceof Collection)
items = (Collection) value;
else if (value instanceof Object[])
items = Arrays.asList((Object[]) value);
else
items = null;
if (items == null && !(value instanceof Iterator))
throw new TemplateException("forEach items neither a Collection nor an Iterator: " + itemsExpression + " => " + value.getClass() + ":" + value);
List targets = tag.getChildren();
List<Element> parentContent = tag.getParentElement().getContent();
int iterationCount = 0;
int index = parentContent.indexOf(tag);
// ne peut utiliser de Map si plusieurs fois le même éléments dans 'items'
final List<Object> itemsList = items == null ? new ArrayList<Object>() : new ArrayList<Object>(items.size());
final List<List<Element>> createdElems = items == null ? new ArrayList<List<Element>>() : new ArrayList<List<Element>>(items.size());
final Iterator itemsIter = (Iterator) (items != null ? items.iterator() : value);
// first we create all elements
while (itemsIter.hasNext()) {
final Object item = itemsIter.next();
final List toAdd;
if (multiple) {
toAdd = targets;
} else {
toAdd = Collections.singletonList(targets.get(iterationCount % targets.size()));
++iterationCount;
}
final List<Element> added = new ArrayList<Element>(toAdd.size());
final Iterator targetIter = toAdd.iterator();
while (targetIter.hasNext()) {
final Element target = (Element) targetIter.next();
Element newElement = (Element) target.clone();
parentContent.add(index++, newElement);
added.add(newElement);
}
// store the new elements and their associated item
itemsList.add(item);
createdElems.add(added);
}
// then transform them
// forced to do that otherwise if we transform right after adding, the newly added element
// might be removed and this will mess with index++
final ListIterator<Object> i = itemsList.listIterator();
while (i.hasNext()) {
final Object item = i.next();
model.put(varName, item);
final Iterator addedIter = createdElems.get(i.previousIndex()).iterator();
while (addedIter.hasNext()) {
final Element newElement = (Element) addedIter.next();
processor.transform(newElement);
removeSection(newElement);
}
}
tag.detach();
}
}