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    }