001 /*
002 *
003 * $Revision: 15295 $ $Date: 2009-05-29 15:15:55 +0200 (Fri, 29 May 2009) $
004 *
005 * This file is part of *** M y C o R e ***
006 * See http://www.mycore.de/ for details.
007 *
008 * This program is free software; you can use it, redistribute it
009 * and / or modify it under the terms of the GNU General Public License
010 * (GPL) as published by the Free Software Foundation; either version 2
011 * of the License or (at your option) any later version.
012 *
013 * This program is distributed in the hope that it will be useful, but
014 * WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program, in a file called gpl.txt or license.txt.
020 * If not, write to the Free Software Foundation Inc.,
021 * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
022 */
023
024 package org.mycore.frontend.editor;
025
026 import java.util.ArrayList;
027 import java.util.Collections;
028 import java.util.Enumeration;
029 import java.util.HashMap;
030 import java.util.Hashtable;
031 import java.util.Iterator;
032 import java.util.List;
033
034 import org.apache.commons.fileupload.FileItem;
035 import org.apache.log4j.Logger;
036 import org.jdom.Attribute;
037 import org.jdom.Document;
038 import org.jdom.Element;
039 import org.jdom.JDOMException;
040 import org.jdom.Namespace;
041 import org.jdom.filter.ElementFilter;
042 import org.jdom.output.Format;
043 import org.jdom.output.XMLOutputter;
044 import org.jdom.xpath.XPath;
045 import org.mycore.common.MCRConfigurationException;
046 import org.mycore.common.MCRConstants;
047
048 /**
049 * Container class that holds all data and files edited and submitted from an
050 * HTML page that contains a MyCoRe XML editor form.
051 *
052 * @author Frank Lützenkirchen
053 * @version $Revision: 15295 $ $Date: 2009-05-29 15:15:55 +0200 (Fri, 29 May 2009) $
054 */
055 public class MCREditorSubmission {
056 private final static Logger LOGGER = Logger.getLogger(MCREditorSubmission.class);
057
058 private List variables = new ArrayList();
059
060 private List repeats = new ArrayList();
061
062 private List files = new ArrayList();
063
064 private Hashtable failed = new Hashtable();
065
066 private Hashtable node2file = new Hashtable();
067
068 private Hashtable file2node = new Hashtable();
069
070 private MCRRequestParameters parms;
071
072 private Document xml;
073
074 private String rootName;
075
076 private final static String ATTR_SEP = "__";
077
078 private final static String BLANK = " ";
079
080 private final static String BLANK_ESCAPED = "_-_";
081
082 /**
083 * Set variables from source xml file that should be edited
084 *
085 * @param input
086 * the root element of the XML input
087 * @param editor
088 * the editor definition
089 */
090 MCREditorSubmission(Element input, Element editor) {
091 setAdditionalNamespaces(editor);
092 buildAttribTable(editor);
093 rootName = input.getName();
094 setVariablesFromXML("", input, new Hashtable());
095 setRepeatsFromVariables();
096 }
097
098 MCREditorSubmission(MCRRequestParameters parms, Element editor, boolean validate) {
099 this.parms = parms;
100 rootName = parms.getParameter("_root");
101 setVariablesFromSubmission(parms, editor);
102 Collections.sort(variables);
103 setRepeatsFromSubmission();
104 setAdditionalNamespaces(editor);
105 if (validate)
106 validate(parms, editor);
107 }
108
109 MCREditorSubmission(Element saved, List submitted, String root, MCRRequestParameters parms) {
110 Element input = saved.getChild("input");
111 List children = input.getChildren();
112
113 String varpath = parms.getParameter("subselect.varpath");
114 boolean merge = "true".equals(parms.getParameter("subselect.merge"));
115
116 Hashtable<String, String> table = new Hashtable<String, String>();
117
118 for (int i = 0; i < children.size(); i++) {
119 Element var = (Element) (children.get(i));
120 String path = var.getAttributeValue("name");
121 String value = var.getAttributeValue("value");
122
123 if (merge)
124 table.put(path, value);
125 else if (path.equals(varpath) || path.startsWith(varpath + "/"))
126 continue;
127 else
128 table.put(path, value);
129 }
130
131 for (int i = 0; i < submitted.size(); i++) {
132 MCREditorVariable var = (MCREditorVariable) (submitted.get(i));
133 String path = var.getPath();
134 String value = var.getValue();
135 path = varpath + path.substring(root.length());
136 table.put(path, value);
137 }
138
139 for (Iterator<String> it = table.keySet().iterator(); it.hasNext();) {
140 String path = it.next();
141 String value = table.get(path);
142 addVariable(path, value);
143 }
144
145 Collections.sort(variables);
146 setRepeatsFromVariables();
147 }
148
149 MCREditorSubmission(Element editor) {
150 Element input = editor.getChild("input");
151 List children = input.getChildren();
152
153 for (int i = 0; i < children.size(); i++) {
154 Element var = (Element) (children.get(i));
155 String path = var.getAttributeValue("name");
156 String value = var.getAttributeValue("value");
157 addVariable(path, value);
158 }
159
160 Collections.sort(variables);
161 setAdditionalNamespaces(editor);
162 setRepeatsFromVariables();
163 }
164
165 private String getNamespacePrefix(Namespace ns) {
166 if ((ns == null) || ns.equals(Namespace.NO_NAMESPACE))
167 return "";
168 Iterator<String> it = nsMap.keySet().iterator();
169 while (it.hasNext()) {
170 String key = (String) (it.next());
171 if (ns.equals(nsMap.get(key)))
172 return key + ":";
173 }
174 String msg = "Namespace " + ns.getURI() + " used in editor source input, but not declared in editor definition. Using: "
175 + ns.getPrefix();
176 LOGGER.warn(msg);
177 return ns.getPrefix() + ":";
178 }
179
180 private void setVariablesFromXML(String prefix, Element element, Hashtable predecessors) {
181 String key = getNamespacePrefix(element.getNamespace()) + element.getName();
182
183 setVariablesFromXML(prefix, key, element, predecessors);
184
185 List attributes = element.getAttributes();
186 for (int i = 0; i < attributes.size(); i++) {
187 Attribute attribute = (Attribute) (attributes.get(i));
188 String name = getNamespacePrefix(attribute.getNamespace()) + attribute.getName();
189 String value = attribute.getValue().replace(BLANK, BLANK_ESCAPED);
190 if ((value == null) || (value.length() == 0))
191 continue;
192 key = getNamespacePrefix(element.getNamespace()) + element.getName() + ATTR_SEP + name + ATTR_SEP + value;
193 if (constraints.containsKey(key))
194 setVariablesFromXML(prefix, key, element, predecessors);
195 }
196 }
197
198 private void setVariablesFromXML(String prefix, String key, Element element, Hashtable predecessors) {
199 int pos = 1;
200 if (predecessors.containsKey(key))
201 pos = ((Integer) (predecessors.get(key))).intValue() + 1;
202 predecessors.put(key, new Integer(pos));
203
204 String path = prefix + "/" + key;
205 if (pos > 1)
206 path = path + "[" + pos + "]";
207
208 // Add element text
209 addVariable(path, element.getText());
210
211 // Add value of all attributes
212 List attributes = element.getAttributes();
213 for (int i = 0; i < attributes.size(); i++) {
214 Attribute attribute = (Attribute) (attributes.get(i));
215 String value = attribute.getValue();
216 if ((value != null) && (value.length() > 0))
217 addVariable(path + "/@" + getNamespacePrefix(attribute.getNamespace()) + attribute.getName(), value);
218 }
219
220 // Add values of all children
221 predecessors = new Hashtable();
222 List children = element.getChildren();
223 for (int i = 0; i < children.size(); i++) {
224 Element child = (Element) (children.get(i));
225 setVariablesFromXML(path, child, predecessors);
226 }
227 }
228
229 /** Constraints on attributes, e.g. title[@type='main'] */
230 private Hashtable constraints;
231
232 /** Fills a table of constraints on attributes, e.g. title[@type='main'] */
233 private void buildAttribTable(Element root) {
234 constraints = new Hashtable();
235 Iterator iter = root.getDescendants(new ElementFilter());
236 while (iter.hasNext()) {
237 Element elem = (Element) (iter.next());
238 String var = elem.getAttributeValue("var");
239 if ((var != null) && (var.indexOf("[@") > 0)) {
240 int pos1 = var.indexOf("[@");
241 int pos2 = var.indexOf("=", pos1);
242 int pos3 = var.indexOf("]", pos2);
243 String name = var.substring(0, pos1).trim();
244 String attr = var.substring(pos1 + 2, pos2).trim();
245 String value = var.substring(pos2 + 2, pos3 - 1).trim().replace(BLANK, BLANK_ESCAPED);
246 if (name.indexOf("/") >= 0)
247 name = name.substring(name.lastIndexOf("/") + 1).trim();
248 String key = name + ATTR_SEP + attr + ATTR_SEP + value;
249 constraints.put(key, value);
250 }
251 }
252 }
253
254 private void setVariablesFromSubmission(MCRRequestParameters parms, Element editor) {
255 for (Enumeration e = parms.getParameterNames(); e.hasMoreElements();) {
256 String name = (String) (e.nextElement());
257
258 if (name.startsWith("_")) {
259 continue; // Skip internal request params
260 }
261
262 String[] values = parms.getParameterValues(name);
263 String sortNr = parms.getParameter("_sortnr-" + name);
264 String ID = parms.getParameter("_id@" + name);
265
266 // Skip files that should be deleted
267 String delete = parms.getParameter("_delete-" + name);
268
269 if ("true".equals(delete) && (parms.getFileItem(name) == null)) {
270 continue;
271 }
272
273 // Skip request params that are not input but target params
274 if (sortNr == null) {
275 continue;
276 }
277
278 // For each value
279 for (int k = 0; (values != null) && (k < values.length); k++) {
280 String value = values[k];
281
282 if ((value == null) || (value.trim().length() == 0)) {
283 continue;
284 }
285
286 // Handle multiple variables with same name: checkboxes & select
287 // multiple
288 String nname = ((k == 0) ? name : (name + "[" + (k + 1) + "]"));
289
290 MCREditorVariable var = new MCREditorVariable(nname, value);
291 var.setSortNr(sortNr);
292
293 // Add associated component from editor definition
294 if ((ID != null) && (ID.trim().length() > 0)) {
295 Element component = MCREditorDefReader.findElementByID(ID, editor);
296
297 if (component != null) {
298 // Skip variables with values equal to autofill text
299 String attrib = component.getAttributeValue("autofill");
300 String elem = component.getChildTextTrim("autofill");
301 String autofill = null;
302
303 if ((attrib != null) && (attrib.trim().length() > 0)) {
304 autofill = attrib.trim();
305 } else if ((attrib != null) && (attrib.trim().length() > 0)) {
306 autofill = elem.trim();
307 }
308
309 if (value.trim().equals(autofill)) {
310 continue;
311 }
312 }
313 }
314
315 variables.add(var);
316
317 FileItem file = parms.getFileItem(name);
318
319 if (file != null) // Add associated uploaded file if it exists
320 {
321 var.setFile(file);
322 files.add(file);
323 }
324 }
325 }
326 }
327
328 private void validate(MCRRequestParameters parms, Element editor) {
329 LOGGER.info("Validating editor input... ");
330
331 for (Enumeration e = parms.getParameterNames(); e.hasMoreElements();) {
332 String name = (String) (e.nextElement());
333
334 if (!name.startsWith("_sortnr-")) {
335 continue;
336 }
337
338 name = name.substring(8);
339
340 String ID = parms.getParameter("_id@" + name);
341
342 if ((ID == null) || (ID.trim().length() == 0)) {
343 continue;
344 }
345
346 String[] values = { "" };
347
348 if (parms.getParameterValues(name) != null) {
349 values = parms.getParameterValues(name);
350 }
351
352 Element component = MCREditorDefReader.findElementByID(ID, editor);
353
354 if (component == null) {
355 continue;
356 }
357
358 List conditions = component.getChildren("condition");
359
360 if (conditions == null) {
361 continue;
362 }
363
364 // Skip variables with values equal to autofill text
365 String attrib = component.getAttributeValue("autofill");
366 String elem = component.getChildTextTrim("autofill");
367 String autofill = null;
368
369 if ((attrib != null) && (attrib.trim().length() > 0)) {
370 autofill = attrib.trim();
371 } else if ((attrib != null) && (attrib.trim().length() > 0)) {
372 autofill = elem.trim();
373 }
374
375 if (values[0].trim().equals(autofill)) {
376 values[0] = "";
377 }
378
379 for (int i = 0; i < conditions.size(); i++) {
380 Element condition = (Element) (conditions.get(i));
381
382 boolean ok = true;
383 for (int j = 0; (j < values.length) && ok; j++) {
384 String nname = ((j == 0) ? name : (name + "[" + (j + 1) + "]"));
385 ok = checkCondition(condition, nname, values[j]);
386
387 if (!ok) {
388 String sortNr = parms.getParameter("_sortnr-" + name);
389 failed.put(sortNr, condition);
390
391 if (LOGGER.isDebugEnabled()) {
392 String cond = new XMLOutputter(Format.getCompactFormat()).outputString(condition);
393 LOGGER.debug("Validation condition failed:");
394 LOGGER.debug(nname + " = \"" + values[j] + "\"");
395 LOGGER.debug(cond);
396 }
397 }
398 }
399 }
400 }
401
402 for (Enumeration e = parms.getParameterNames(); e.hasMoreElements();) {
403 String name = (String) (e.nextElement());
404
405 if (!name.startsWith("_cond-")) {
406 continue;
407 }
408
409 String path = name.substring(6);
410 String[] ids = parms.getParameterValues(name);
411
412 if (ids != null) {
413 for (int i = 0; i < ids.length; i++) {
414 String id = ids[i];
415 Element condition = MCREditorDefReader.findElementByID(id, editor);
416
417 if (condition == null) {
418 continue;
419 }
420
421 String pathA = path + "/" + condition.getAttributeValue("field1");
422 String pathB = path + "/" + condition.getAttributeValue("field2");
423 String valueA = parms.getParameter(pathA);
424 String valueB = parms.getParameter(pathB);
425
426 String type = condition.getAttributeValue("type");
427 String oper = condition.getAttributeValue("operator");
428 String format = condition.getAttributeValue("format");
429
430 String clazz = condition.getAttributeValue("class");
431 String method = condition.getAttributeValue("method");
432
433 boolean ok = true;
434
435 if ((oper != null) && (oper.length() > 0))
436 ok = MCRInputValidator.instance().compare(valueA, valueB, oper, type, format);
437 if ((clazz != null) && (clazz.length() > 0) && (condition.getAttributeValue("field1") != null)
438 && (condition.getAttributeValue("field2") != null))
439 ok = ok && MCRInputValidator.instance().validateExternally(clazz, method, valueA, valueB);
440
441 if (!ok) {
442 String sortNrA = parms.getParameter("_sortnr-" + pathA);
443 failed.put(sortNrA, condition);
444
445 String sortNrB = parms.getParameter("_sortnr-" + pathB);
446 failed.put(sortNrB, condition);
447
448 if (LOGGER.isDebugEnabled()) {
449 String cond = new XMLOutputter(Format.getCompactFormat()).outputString(condition);
450 LOGGER.debug("Validation condition failed:");
451 LOGGER.debug(pathA + " " + oper + " " + pathB);
452 LOGGER.debug(cond);
453 }
454 } else if (condition.getAttribute("field1") == null) {
455 Element current = null;
456
457 try {
458 String xslcond = condition.getAttributeValue("xsl");
459 if ((xslcond != null) && (xslcond.length() > 0)) {
460 current = (Element) (XPath.selectSingleNode(this.getXML(), path));
461 ok = MCRInputValidator.instance().validateXSLCondition(current, xslcond);
462 if ((!ok) && LOGGER.isDebugEnabled()) {
463 String xml = new XMLOutputter(Format.getPrettyFormat()).outputString(current);
464 LOGGER.debug("Validation condition failed:");
465 LOGGER.debug("Context xpath: " + path);
466 LOGGER.debug("XSL condition: " + xslcond);
467 LOGGER.debug(xml);
468 }
469 }
470 if ((clazz != null) && (clazz.length() > 0)) {
471 if (current == null)
472 current = (Element) (XPath.selectSingleNode(this.getXML(), path));
473 ok = ok && MCRInputValidator.instance().validateExternally(clazz, method, current);
474 }
475 } catch (JDOMException ex) {
476 LOGGER.debug("Could not validate, because no element found at xpath " + path);
477 continue;
478 }
479 if (!ok) {
480 String sortNr = parms.getParameter("_sortnr-" + path);
481 failed.put(sortNr, condition);
482 }
483 }
484 }
485 }
486 }
487 }
488
489 private boolean checkCondition(Element condition, String name, String value) {
490 boolean required = "true".equals(condition.getAttributeValue("required"));
491
492 if (required) {
493 if (name.endsWith("]") && ((value == null) || (value.trim().length() == 0))) {
494 return true; // repeated field is required but missing, this
495 // is
496 }
497 // ok
498 else if (!MCRInputValidator.instance().validateRequired(value)) {
499 return false; // field is required but empty, this is an error
500 }
501 } else if ((!required) && (value.trim().length() == 0)) {
502 return true; // field is not required and empty, this is OK
503 }
504
505 String type = condition.getAttributeValue("type");
506 String min = condition.getAttributeValue("min");
507 String max = condition.getAttributeValue("max");
508 String format = condition.getAttributeValue("format");
509
510 if ((type != null) && !MCRInputValidator.instance().validateMinMaxType(value, type, min, max, format)) {
511 return false; // field type, data format and/or min max value is
512 // illegal
513 }
514
515 String minLength = condition.getAttributeValue("minLength");
516 String maxLength = condition.getAttributeValue("maxLength");
517
518 if (((maxLength != null) || (minLength != null)) && !MCRInputValidator.instance().validateLength(value, minLength, maxLength)) {
519 return false; // field min/max length is illegal
520 }
521
522 String regexp = condition.getAttributeValue("regexp");
523
524 if ((regexp != null) && !MCRInputValidator.instance().validateRegularExpression(value, regexp)) {
525 return false; // field does not match given regular expression
526 }
527
528 String xsl = condition.getAttributeValue("xsl");
529
530 if ((xsl != null) && !MCRInputValidator.instance().validateXSLCondition(value, xsl)) {
531 return false; // field does not match given xsl condition
532 }
533
534 String clazz = condition.getAttributeValue("class");
535 String method = condition.getAttributeValue("method");
536
537 if ((clazz != null) && (method != null) && !MCRInputValidator.instance().validateExternally(clazz, method, value)) {
538 return false; // field does not validate using external method
539 }
540
541 return true;
542 }
543
544 private void addVariable(String path, String text) {
545 if ((text == null) || (text.trim().length() == 0)) {
546 return;
547 }
548
549 MCREditorServlet.logger.debug("Editor variable " + path + "=" + text);
550 variables.add(new MCREditorVariable(path, text));
551 }
552
553 public List getVariables() {
554 return variables;
555 }
556
557 public List getFiles() {
558 return files;
559 }
560
561 public FileItem getFile(Object xmlNode) {
562 if (xml == null) {
563 buildTargetXML();
564 }
565
566 return (FileItem) (node2file.get(xmlNode));
567 }
568
569 public Object getXMLNode(FileItem file) {
570 if (xml == null) {
571 buildTargetXML();
572 }
573
574 return file2node.get(file);
575 }
576
577 public Document getXML() {
578 if (xml == null) {
579 buildTargetXML();
580 }
581
582 return xml;
583 }
584
585 Element buildInputElements() {
586 Element input = new Element("input");
587
588 for (int i = 0; i < variables.size(); i++) {
589 MCREditorVariable var = (MCREditorVariable) (variables.get(i));
590 input.addContent(var.asInputElement());
591 }
592
593 return input;
594 }
595
596 Element buildRepeatElements() {
597 Element eRepeats = new Element("repeats");
598
599 for (int i = 0; i < repeats.size(); i++) {
600 MCREditorVariable var = (MCREditorVariable) (repeats.get(i));
601 eRepeats.addContent(var.asRepeatElement());
602 }
603
604 return eRepeats;
605 }
606
607 Element buildFailedConditions() {
608 if (failed.isEmpty()) {
609 return null;
610 }
611
612 Element failedConds = new Element("failed");
613
614 for (Enumeration e = failed.keys(); e.hasMoreElements();) {
615 String sortNr = (String) (e.nextElement());
616 Element condition = (Element) (failed.get(sortNr));
617 Element field = new Element("field");
618 field.setAttribute("sortnr", sortNr);
619 field.setAttribute("condition", condition.getAttributeValue("id"));
620 failedConds.addContent(field);
621 }
622
623 failed = new Hashtable();
624
625 return failedConds;
626 }
627
628 boolean errors() {
629 return !failed.isEmpty();
630 }
631
632 public MCRRequestParameters getParameters() {
633 return parms;
634 }
635
636 private void buildTargetXML() {
637 Element root;
638 if (variables.size() > 0)
639 root = buildElement(((MCREditorVariable) (variables.get(0))).getPathElements()[0]);
640 else
641 root = buildElement(rootName.replace("/", ""));
642
643 for (int i = 0; i < variables.size(); i++) {
644 MCREditorVariable var = (MCREditorVariable) (variables.get(i));
645
646 Element parent = root;
647 String[] elements = var.getPathElements();
648
649 for (int j = 1; j < elements.length; j++) {
650 String name = elements[j];
651
652 if (name.endsWith("]")) {
653 int pos = name.lastIndexOf("[");
654 name = name.substring(0, pos) + "_XXX_" + name.substring(pos + 1, name.length() - 1);
655 }
656
657 Namespace ns = getNamespace(name);
658 if (ns != null)
659 name = name.substring(name.indexOf(":") + 1);
660 Element child = parent.getChild(name, ns);
661
662 if (child == null) {
663 child = new Element(name, ns);
664 parent.addContent(child);
665 }
666
667 parent = child;
668 }
669
670 Object node;
671
672 if (!var.isAttribute()) {
673 parent.addContent(var.getValue());
674 node = parent;
675 } else {
676 LOGGER.debug("Setting attribute " + var.getPath() + " = " + var.getValue());
677 setAttribute(parent, var.getAttributeName(), var.getValue());
678 node = parent.getAttribute(var.getAttributeName());
679 }
680
681 FileItem file = (parms == null ? null : parms.getFileItem(var.getPath()));
682
683 if (file != null) {
684 file2node.put(file, node);
685 node2file.put(node, file);
686 }
687 }
688
689 renameRepeatedElements(root);
690 xml = new Document(root);
691 }
692
693 /**
694 * A map from namespace prefix to namespace for the namespaces registered in
695 * the editor definition.
696 */
697 private HashMap<String, Namespace> nsMap = new HashMap<String, Namespace>();
698
699 /**
700 * Stores the list of additional namespaces declared in the components
701 * element of the editor definition. These namespaces and its prefixes can
702 * be used in editor variable paths (var attributes of cells).
703 */
704 @SuppressWarnings("unchecked")
705 private void setAdditionalNamespaces(Element editor) {
706 Element components = editor.getChild("components");
707 List<Namespace> namespaces = components.getAdditionalNamespaces();
708 for (Namespace ns : namespaces)
709 nsMap.put(ns.getPrefix(), ns);
710 nsMap.put("xml", Namespace.XML_NAMESPACE);
711 setNamespaceIfUndefined(MCRConstants.DV_NAMESPACE);
712 setNamespaceIfUndefined(MCRConstants.METS_NAMESPACE);
713 setNamespaceIfUndefined(MCRConstants.MODS_NAMESPACE);
714 setNamespaceIfUndefined(MCRConstants.XLINK_NAMESPACE);
715 setNamespaceIfUndefined(MCRConstants.XSI_NAMESPACE);
716 setNamespaceIfUndefined(MCRConstants.XSL_NAMESPACE);
717 }
718
719 private void setNamespaceIfUndefined(Namespace namespace) {
720 if (!nsMap.containsKey(namespace.getPrefix()))
721 nsMap.put(namespace.getPrefix(), namespace);
722 }
723
724 /**
725 * Extracts namespace prefix from the given name (the part before the ":")
726 * and resolves it to the namespace registered in the editor definition.
727 */
728 private Namespace getNamespace(String name) {
729 int pos1 = name.indexOf(":");
730 int pos2 = name.indexOf(ATTR_SEP);
731 if ((pos1 == -1) || ((pos1 > pos2) && (pos2 >= 0)))
732 return null;
733 String prefix = name.substring(0, pos1);
734 if (!nsMap.containsKey(prefix)) {
735 String msg = "Namespace prefix " + prefix + " is used in editor variable, but not defined";
736 throw new MCRConfigurationException(msg);
737 }
738 return nsMap.get(prefix);
739 }
740
741 /**
742 * Builds a new XML element for data output. The name may contain a
743 * namespace prefix, which is resolved to a namespace then.
744 */
745 private Element buildElement(String name) {
746 Namespace ns = getNamespace(name);
747 if (ns != null)
748 name = name.substring(name.indexOf(":") + 1);
749 return new Element(name, ns);
750 }
751
752 /**
753 * Sets attribute value of the given parent element. The name may contain a
754 * namespace prefix, which is resolved to a namespace then.
755 */
756 private void setAttribute(Element parent, String name, String value) {
757 Namespace ns = getNamespace(name);
758 if (ns != null) {
759 name = name.substring(name.indexOf(":") + 1);
760 parent.setAttribute(name, value, ns);
761 } else
762 parent.setAttribute(name, value);
763 }
764
765 private void renameRepeatedElements(Element element) {
766 String name = element.getName();
767 int pos = name.lastIndexOf("_XXX_");
768
769 if (pos >= 0) {
770 name = name.substring(0, pos);
771 element.setName(name);
772 }
773
774 pos = name.indexOf(ATTR_SEP);
775 if (pos > 0) {
776 element.setName(name.substring(0, pos));
777 int pos2 = name.indexOf(ATTR_SEP, pos + 2);
778 String attr = name.substring(pos + 2, pos2);
779 String val = name.substring(pos2 + 2).replace(BLANK_ESCAPED, BLANK);
780 setAttribute(element, attr, val);
781 }
782
783 List children = element.getChildren();
784
785 for (int i = 0; i < children.size(); i++)
786 renameRepeatedElements((Element) (children.get(i)));
787 }
788
789 private void setRepeatsFromVariables() {
790 Hashtable maxtable = new Hashtable();
791
792 for (int i = 0; i < variables.size(); i++) {
793 MCREditorVariable var = (MCREditorVariable) (variables.get(i));
794 String[] path = var.getPathElements();
795 String prefix = "/" + path[0];
796
797 for (int j = 1; j < path.length; j++) {
798 String name = path[j];
799 int pos1 = name.lastIndexOf("[");
800 int pos2 = name.lastIndexOf("]");
801
802 if (pos1 != -1) {
803 String elem = name.substring(0, pos1);
804 String num = name.substring(pos1 + 1, pos2);
805 String key = prefix + "/" + elem;
806
807 int numNew = Integer.parseInt(num);
808
809 if (maxtable.containsKey(key)) {
810 int numOld = Integer.parseInt((String) (maxtable.get(key)));
811 maxtable.remove(key);
812 numNew = Math.max(numOld, numNew);
813 }
814
815 maxtable.put(key, String.valueOf(numNew));
816 }
817
818 prefix = prefix + "/" + name;
819 }
820 }
821
822 for (Enumeration e = maxtable.keys(); e.hasMoreElements();) {
823 String path = (String) (e.nextElement());
824 String value = (String) (maxtable.get(path));
825
826 repeats.add(new MCREditorVariable(path, value));
827 MCREditorServlet.logger.debug("Editor repeats " + path + " = " + value);
828 }
829 }
830
831 private void setRepeatsFromSubmission() {
832 for (Enumeration e = parms.getParameterNames(); e.hasMoreElements();) {
833 String parameter = (String) (e.nextElement());
834
835 if (parameter.startsWith("_n-")) {
836 String value = parms.getParameter(parameter);
837 repeats.add(new MCREditorVariable(parameter.substring(3), value));
838 MCREditorServlet.logger.debug("Editor repeats " + parameter.substring(3) + " = " + value);
839 }
840 }
841 }
842
843 void doPlus(String prefix, int nr) {
844 changeRepeatNumber(prefix, +1);
845 changeVariablesAndRepeats(prefix, nr, +1);
846 }
847
848 void doMinus(String prefix, int nr) {
849 changeRepeatNumber(prefix, -1);
850
851 String prefix2;
852
853 if (nr > 1) {
854 prefix2 = prefix + "[" + nr + "]";
855 } else {
856 prefix2 = prefix;
857 }
858
859 for (int i = 0; i < variables.size(); i++) {
860 String path = ((MCREditorVariable) (variables.get(i))).getPath();
861
862 if (path.startsWith(prefix2 + "/") || path.equals(prefix2)) {
863 variables.remove(i--);
864 }
865 }
866
867 for (int i = 0; i < repeats.size(); i++) {
868 String path = ((MCREditorVariable) (repeats.get(i))).getPath();
869
870 if (path.startsWith(prefix2 + "/")) {
871 repeats.remove(i--);
872 }
873 }
874
875 changeVariablesAndRepeats(prefix, nr, -1);
876 }
877
878 void doUp(String prefix, int nr) {
879 String prefix1 = prefix + ((nr > 2) ? ("[" + String.valueOf(nr - 1) + "]") : "");
880 String prefix2 = prefix + "[" + String.valueOf(nr) + "]";
881
882 for (int i = 0; i < variables.size(); i++) {
883 MCREditorVariable var = (MCREditorVariable) (variables.get(i));
884 String path = var.getPath();
885
886 if (path.startsWith(prefix1 + "/") || path.equals(prefix1)) {
887 String rest = path.substring(prefix1.length());
888 var.setPath(prefix2 + rest);
889 } else if (path.startsWith(prefix2) || path.equals(prefix2)) {
890 String rest = path.substring(prefix2.length());
891 var.setPath(prefix1 + rest);
892 }
893 }
894
895 for (int i = 0; i < repeats.size(); i++) {
896 MCREditorVariable var = (MCREditorVariable) (repeats.get(i));
897 String path = var.getPath();
898
899 if (path.startsWith(prefix1 + "/")) {
900 String rest = path.substring(prefix1.length());
901 var.setPath(prefix2 + rest);
902 } else if (path.startsWith(prefix2 + "/")) {
903 String rest = path.substring(prefix2.length());
904 var.setPath(prefix1 + rest);
905 }
906 }
907 }
908
909 void changeRepeatNumber(String prefix, int change) {
910 for (int i = 0; i < repeats.size(); i++) {
911 MCREditorVariable var = (MCREditorVariable) (repeats.get(i));
912
913 if (var.getPath().equals(prefix)) {
914 int value = Integer.parseInt(var.getValue()) + change;
915
916 if (value == 0) {
917 value = 1;
918 }
919
920 var.setValue(String.valueOf(value));
921
922 return;
923 }
924 }
925 }
926
927 void changeVariablesAndRepeats(String prefix, int nr, int change) {
928 ArrayList list = new ArrayList();
929 list.addAll(variables);
930 list.addAll(repeats);
931
932 for (int i = 0; i < list.size(); i++) {
933 MCREditorVariable var = (MCREditorVariable) (list.get(i));
934 String path = var.getPath();
935
936 if (!path.startsWith(prefix + "[")) {
937 continue;
938 }
939
940 String rest = path.substring(prefix.length() + 1);
941
942 int pos = rest.indexOf("]");
943 int num = Integer.parseInt(rest.substring(0, pos));
944
945 if (num > nr) {
946 num += change;
947
948 StringBuffer newpath = new StringBuffer(prefix);
949
950 if (num > 1) {
951 newpath.append("[").append(num).append("]");
952 }
953
954 newpath.append(rest.substring(pos + 1));
955
956 var.setPath(newpath.toString());
957 }
958 }
959 }
960 }