/*******************************************************************************
 * Copyright (c) 2010, 2016 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Patrick Tasse - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.internal.tmf.ui.parsers.wizards;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.ParseException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.tracecompass.common.core.xml.XmlUtils;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.ui.Messages;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomTraceDefinition;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomTraceDefinition.Tag;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomXmlInputAttribute;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomXmlInputElement;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomXmlTrace;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomXmlTraceDefinition;
import org.eclipse.tracecompass.tmf.core.project.model.TmfTraceType;
import org.eclipse.tracecompass.tmf.core.project.model.TraceTypeHelper;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.google.common.base.Joiner;

/**
 * Input wizard page for custom XML trace parsers.
 *
 * @author Patrick Tasse
 */
public class CustomXmlParserInputWizardPage extends WizardPage {

    private static final String DEFAULT_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; //$NON-NLS-1$
    private static final String TIMESTAMP_FORMAT_BUNDLE = "org.eclipse.tracecompass.doc.user"; //$NON-NLS-1$
    private static final String TIMESTAMP_FORMAT_PATH = "reference/api/org/eclipse/tracecompass/tmf/core/timestamp/TmfTimestampFormat.html"; //$NON-NLS-1$
    private static final Image ELEMENT_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/element_icon.gif"); //$NON-NLS-1$
    private static final Image ADD_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/add_button.gif"); //$NON-NLS-1$
    private static final Image ADD_NEXT_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/addnext_button.gif"); //$NON-NLS-1$
    private static final Image ADD_CHILD_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/addchild_button.gif"); //$NON-NLS-1$
    private static final Image ADD_MANY_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/addmany_button.gif"); //$NON-NLS-1$
    private static final Image DELETE_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/delete_button.gif"); //$NON-NLS-1$
    private static final Image MOVE_UP_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/moveup_button.gif"); //$NON-NLS-1$
    private static final Image MOVE_DOWN_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/movedown_button.gif"); //$NON-NLS-1$
    private static final Image HELP_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/help_button.gif"); //$NON-NLS-1$
    private static final Color COLOR_LIGHT_RED = new Color(Display.getDefault(), 255, 192, 192);
    private static final Color COLOR_TEXT_BACKGROUND = Display.getDefault().getSystemColor(SWT.COLOR_WHITE);
    private static final Color COLOR_WIDGET_BACKGROUND = Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
    private static final Color COLOR_GRAY = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);

    private final ISelection selection;
    private CustomXmlTraceDefinition definition;
    private String editCategoryName;
    private String editDefinitionName;
    private String defaultDescription;
    private ElementNode selectedElement;
    private Composite container;
    private Text categoryText;
    private Text logtypeText;
    private Text timeStampOutputFormatText;
    private Text timeStampPreviewText;
    private Button removeButton;
    private Button addChildButton;
    private Button addNextButton;
    private Button moveUpButton;
    private Button moveDownButton;
    private ScrolledComposite elementScrolledComposite;
    private TreeViewer treeViewer;
    private Composite elementContainer;
    private Text errorText;
    private StyledText inputText;
    private Font fixedFont;
    private UpdateListener updateListener;
    private Browser helpBrowser;
    private Element documentElement;

    // variables used recursively through element traversal
    private String timeStampValue;
    private String timeStampFormat;
    private boolean timeStampFound;
    private int logEntriesCount;
    private boolean logEntryFound;

    /**
     * Constructor
     *
     * @param selection
     *            Selection object
     * @param definition
     *            Trace definition
     */
    protected CustomXmlParserInputWizardPage(ISelection selection, CustomXmlTraceDefinition definition) {
        super("CustomXmlParserWizardPage"); //$NON-NLS-1$
        if (definition == null) {
            setTitle(Messages.CustomXmlParserInputWizardPage_titleNew);
            defaultDescription = Messages.CustomXmlParserInputWizardPage_descriptionNew;
        } else {
            setTitle(Messages.CustomXmlParserInputWizardPage_titleEdit);
            defaultDescription = Messages.CustomXmlParserInputWizardPage_descriptionEdit;
        }
        setDescription(defaultDescription);
        this.selection = selection;
        this.definition = definition;
        if (definition != null) {
            this.editCategoryName = definition.categoryName;
            this.editDefinitionName = definition.definitionName;
        }
    }

    @Override
    public void createControl(Composite parent) {
        container = new Composite(parent, SWT.NULL);
        container.setLayout(new GridLayout());

        updateListener = new UpdateListener();

        Composite headerComposite = new Composite(container, SWT.FILL);
        GridLayout headerLayout = new GridLayout(5, false);
        headerLayout.marginHeight = 0;
        headerLayout.marginWidth = 0;
        headerComposite.setLayout(headerLayout);
        headerComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        Label categoryLabel = new Label(headerComposite, SWT.NULL);
        categoryLabel.setText(Messages.CustomXmlParserInputWizardPage_category);

        categoryText = new Text(headerComposite, SWT.BORDER | SWT.SINGLE);
        categoryText.setLayoutData(new GridData(120, SWT.DEFAULT));

        Label timeStampFormatLabel = new Label(headerComposite, SWT.NULL);
        timeStampFormatLabel.setText(Messages.CustomXmlParserInputWizardPage_timestampFormat);

        timeStampOutputFormatText = new Text(headerComposite, SWT.BORDER | SWT.SINGLE);
        timeStampOutputFormatText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        timeStampOutputFormatText.setText(DEFAULT_TIMESTAMP_FORMAT);
        timeStampOutputFormatText.addPaintListener(e -> {
            if (!timeStampOutputFormatText.isFocusControl() && timeStampOutputFormatText.getText().trim().isEmpty()) {
                e.gc.setForeground(COLOR_GRAY);
                int borderWidth = timeStampOutputFormatText.getBorderWidth();
                e.gc.drawText(Messages.CustomXmlParserInputWizardPage_default, borderWidth, borderWidth);
            }
        });

        Button timeStampFormatHelpButton = new Button(headerComposite, SWT.PUSH);
        timeStampFormatHelpButton.setImage(HELP_IMAGE);
        timeStampFormatHelpButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_timestampFormatHelp);
        timeStampFormatHelpButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                Bundle plugin = Platform.getBundle(TIMESTAMP_FORMAT_BUNDLE);
                IPath path = new Path(TIMESTAMP_FORMAT_PATH);
                URL fileURL = FileLocator.find(plugin, path, null);
                try {
                    URL pageURL = FileLocator.toFileURL(fileURL);
                    openHelpShell(pageURL.toString());
                } catch (IOException e1) {
                }
            }
        });

        Label logtypeLabel = new Label(headerComposite, SWT.NULL);
        logtypeLabel.setText(Messages.CustomXmlParserInputWizardPage_logType);

        logtypeText = new Text(headerComposite, SWT.BORDER | SWT.SINGLE);
        logtypeText.setLayoutData(new GridData(120, SWT.DEFAULT));
        logtypeText.setFocus();

        Label timeStampPreviewLabel = new Label(headerComposite, SWT.NULL);
        timeStampPreviewLabel.setText(Messages.CustomXmlParserInputWizardPage_preview);

        timeStampPreviewText = new Text(headerComposite, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
        timeStampPreviewText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
        timeStampPreviewText.setText("*no time stamp element or attribute*"); //$NON-NLS-1$

        createButtonBar();

        SashForm vSash = new SashForm(container, SWT.VERTICAL);
        vSash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        vSash.setBackground(COLOR_GRAY);

        SashForm hSash = new SashForm(vSash, SWT.HORIZONTAL);
        hSash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        ScrolledComposite treeScrolledComposite = new ScrolledComposite(hSash, SWT.V_SCROLL | SWT.H_SCROLL);
        treeScrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        Composite treeContainer = new Composite(treeScrolledComposite, SWT.NONE);
        treeContainer.setLayout(new FillLayout());
        treeScrolledComposite.setContent(treeContainer);
        treeScrolledComposite.setExpandHorizontal(true);
        treeScrolledComposite.setExpandVertical(true);

        treeViewer = new TreeViewer(treeContainer, SWT.SINGLE | SWT.BORDER);
        treeViewer.setContentProvider(new InputElementTreeNodeContentProvider());
        treeViewer.setLabelProvider(new InputElementTreeLabelProvider());
        treeViewer.addSelectionChangedListener(new InputElementTreeSelectionChangedListener());
        treeContainer.layout();

        treeScrolledComposite
                .setMinSize(treeContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x, treeContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);

        elementScrolledComposite = new ScrolledComposite(hSash, SWT.V_SCROLL);
        elementScrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        elementContainer = new Composite(elementScrolledComposite, SWT.NONE);
        GridLayout gl = new GridLayout();
        gl.marginHeight = 1;
        gl.marginWidth = 0;
        elementContainer.setLayout(gl);
        elementScrolledComposite.setContent(elementContainer);
        elementScrolledComposite.setExpandHorizontal(true);
        elementScrolledComposite.setExpandVertical(true);

        if (definition == null) {
            definition = new CustomXmlTraceDefinition();
        }
        loadDefinition(definition);
        treeViewer.expandAll();
        elementContainer.layout();

        categoryText.addModifyListener(updateListener);
        logtypeText.addModifyListener(updateListener);
        timeStampOutputFormatText.addModifyListener(updateListener);

        elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);

        hSash.setWeights(new int[] { 1, 2 });

        if (definition.rootInputElement == null) {
            removeButton.setEnabled(false);
            addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addDocumentElement);
            addNextButton.setEnabled(false);
            moveUpButton.setEnabled(false);
            moveDownButton.setEnabled(false);
        } else { // root is selected
            addNextButton.setEnabled(false);
        }

        Composite sashBottom = new Composite(vSash, SWT.NONE);
        GridLayout sashBottomLayout = new GridLayout(2, false);
        sashBottomLayout.marginHeight = 0;
        sashBottomLayout.marginWidth = 0;
        sashBottom.setLayout(sashBottomLayout);

        Label previewLabel = new Label(sashBottom, SWT.NULL);
        previewLabel.setText(Messages.CustomXmlParserInputWizardPage_previewInput);

        errorText = new Text(sashBottom, SWT.SINGLE | SWT.READ_ONLY);
        errorText.setBackground(COLOR_WIDGET_BACKGROUND);
        errorText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        errorText.setVisible(false);

        inputText = new StyledText(sashBottom, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
        if (fixedFont == null) {
            if (System.getProperty("os.name").contains("Windows")) { //$NON-NLS-1$ //$NON-NLS-2$
                fixedFont = new Font(Display.getCurrent(), new FontData("Courier New", 10, SWT.NORMAL)); //$NON-NLS-1$
            } else {
                fixedFont = new Font(Display.getCurrent(), new FontData("Monospace", 10, SWT.NORMAL)); //$NON-NLS-1$
            }
        }
        inputText.setFont(fixedFont);
        GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
        gd.heightHint = inputText.computeSize(SWT.DEFAULT, inputText.getLineHeight() * 4).y;
        gd.widthHint = 800;
        inputText.setLayoutData(gd);
        inputText.setText(getSelectionText());
        inputText.addModifyListener(e -> parseXmlInput(inputText.getText()));
        inputText.addModifyListener(updateListener);

        vSash.setWeights(new int[] { hSash.computeSize(SWT.DEFAULT, SWT.DEFAULT).y, sashBottom.computeSize(SWT.DEFAULT, SWT.DEFAULT).y });

        setControl(container);
    }

    private void createButtonBar() {
        Composite buttonBar = new Composite(container, SWT.NONE);
        GridLayout buttonBarLayout = new GridLayout(6, false);
        buttonBarLayout.marginHeight = 0;
        buttonBarLayout.marginWidth = 0;
        buttonBar.setLayout(buttonBarLayout);

        removeButton = new Button(buttonBar, SWT.PUSH);
        removeButton.setImage(DELETE_IMAGE);
        removeButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_removeElement);
        removeButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (treeViewer.getSelection().isEmpty() || selectedElement == null) {
                    return;
                }
                removeElement();
                CustomXmlInputElement inputElement = (CustomXmlInputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                if (inputElement == definition.rootInputElement) {
                    definition.rootInputElement = null;
                } else {
                    inputElement.getParentElement().getChildElements().remove(inputElement);
                }
                treeViewer.refresh();
                validate();
                updatePreviews();
                removeButton.setEnabled(false);
                if (definition.rootInputElement == null) {
                    addChildButton.setEnabled(true);
                    addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addDocumentEleemnt);
                } else {
                    addChildButton.setEnabled(false);
                }
                addNextButton.setEnabled(false);
                moveUpButton.setEnabled(false);
                moveDownButton.setEnabled(false);
            }
        });

        addChildButton = new Button(buttonBar, SWT.PUSH);
        addChildButton.setImage(ADD_CHILD_IMAGE);
        addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addChildElement);
        addChildButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                CustomXmlInputElement inputElement = new CustomXmlInputElement("", false, Tag.IGNORE, Tag.IGNORE.toString(), 0, "", null); //$NON-NLS-1$ //$NON-NLS-2$
                if (definition.rootInputElement == null) {
                    definition.rootInputElement = inputElement;
                    inputElement.setElementName(getChildNameSuggestion(null));
                } else if (treeViewer.getSelection().isEmpty()) {
                    return;
                } else {
                    CustomXmlInputElement parentInputElement = (CustomXmlInputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                    parentInputElement.addChild(inputElement);
                    inputElement.setElementName(getChildNameSuggestion(parentInputElement));
                }
                treeViewer.refresh();
                treeViewer.setSelection(new StructuredSelection(inputElement), true);
            }
        });

        addNextButton = new Button(buttonBar, SWT.PUSH);
        addNextButton.setImage(ADD_NEXT_IMAGE);
        addNextButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addNextElement);
        addNextButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                CustomXmlInputElement inputElement = new CustomXmlInputElement("", false, Tag.IGNORE, Tag.IGNORE.toString(), 0, "", null); //$NON-NLS-1$ //$NON-NLS-2$
                if (definition.rootInputElement == null) {
                    definition.rootInputElement = inputElement;
                    inputElement.setElementName(getChildNameSuggestion(null));
                } else if (treeViewer.getSelection().isEmpty()) {
                    return;
                } else {
                    CustomXmlInputElement previousInputElement = (CustomXmlInputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                    if (previousInputElement == definition.rootInputElement) {
                        return;
                    }
                    previousInputElement.addNext(inputElement);
                    inputElement.setElementName(getChildNameSuggestion(inputElement.getParentElement()));
                }
                treeViewer.refresh();
                treeViewer.setSelection(new StructuredSelection(inputElement), true);
            }
        });

        Button feelingLuckyButton = new Button(buttonBar, SWT.PUSH);
        feelingLuckyButton.setImage(ADD_MANY_IMAGE);
        feelingLuckyButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_feelingLucky);
        feelingLuckyButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                CustomXmlInputElement inputElement = null;
                if (definition.rootInputElement == null) {
                    if (getChildNameSuggestion(null).length() != 0) {
                        inputElement = new CustomXmlInputElement(getChildNameSuggestion(null), false, Tag.IGNORE, Tag.IGNORE.toString(), 0, "", null); //$NON-NLS-1$
                        definition.rootInputElement = inputElement;
                        feelingLucky(inputElement);
                    } else {
                        return;
                    }
                } else if (treeViewer.getSelection().isEmpty()) {
                    return;
                } else {
                    inputElement = (CustomXmlInputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                    feelingLucky(inputElement);
                }
                treeViewer.refresh();
                treeViewer.setSelection(new StructuredSelection(inputElement), true);
                treeViewer.expandToLevel(inputElement, AbstractTreeViewer.ALL_LEVELS);
            }
        });

        moveUpButton = new Button(buttonBar, SWT.PUSH);
        moveUpButton.setImage(MOVE_UP_IMAGE);
        moveUpButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_moveUp);
        moveUpButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (treeViewer.getSelection().isEmpty()) {
                    return;
                }
                CustomXmlInputElement inputElement = (CustomXmlInputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                if (inputElement == definition.rootInputElement) {
                    return;
                }
                inputElement.moveUp();
                treeViewer.refresh();
                validate();
                updatePreviews();
            }
        });

        moveDownButton = new Button(buttonBar, SWT.PUSH);
        moveDownButton.setImage(MOVE_DOWN_IMAGE);
        moveDownButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_moveDown);
        moveDownButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (treeViewer.getSelection().isEmpty()) {
                    return;
                }
                CustomXmlInputElement inputElement = (CustomXmlInputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                if (inputElement == definition.rootInputElement) {
                    return;
                }
                inputElement.moveDown();
                treeViewer.refresh();
                validate();
                updatePreviews();
            }
        });
    }

    private void feelingLucky(CustomXmlInputElement inputElement) {
        while (true) {
            String attributeName = getAttributeNameSuggestion(inputElement);
            if (attributeName.length() == 0) {
                break;
            }
            CustomXmlInputAttribute attribute = new CustomXmlInputAttribute(attributeName, Tag.OTHER, attributeName, 0, ""); //$NON-NLS-1$
            inputElement.addAttribute(attribute);
        }
        while (true) {
            String childName = getChildNameSuggestion(inputElement);
            if (childName.length() == 0) {
                break;
            }
            CustomXmlInputElement childElement = new CustomXmlInputElement(childName, false, Tag.IGNORE, Tag.IGNORE.toString(), 0, "", null); //$NON-NLS-1$
            inputElement.addChild(childElement);
            feelingLucky(childElement);
        }
    }

    private static class InputElementTreeNodeContentProvider implements ITreeContentProvider {

        @Override
        public Object[] getElements(Object inputElement) {
            CustomXmlTraceDefinition def = (CustomXmlTraceDefinition) inputElement;
            if (def.rootInputElement != null) {
                return new Object[] { def.rootInputElement };
            }
            return new Object[0];
        }

        @Override
        public Object[] getChildren(Object parentElement) {
            CustomXmlInputElement inputElement = (CustomXmlInputElement) parentElement;
            if (inputElement.getChildElements() == null) {
                return new CustomXmlInputElement[0];
            }
            return inputElement.getChildElements().toArray();
        }

        @Override
        public boolean hasChildren(Object element) {
            CustomXmlInputElement inputElement = (CustomXmlInputElement) element;
            return (inputElement.getChildElements() != null && !inputElement.getChildElements().isEmpty());
        }

        @Override
        public void dispose() {
            // Do nothing
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            // Do nothing
        }

        @Override
        public Object getParent(Object element) {
            CustomXmlInputElement inputElement = (CustomXmlInputElement) element;
            return inputElement.getParentElement();
        }
    }

    private static class InputElementTreeLabelProvider extends ColumnLabelProvider {

        @Override
        public Image getImage(Object element) {
            return ELEMENT_IMAGE;
        }

        @Override
        public String getText(Object element) {
            CustomXmlInputElement inputElement = (CustomXmlInputElement) element;
            return (inputElement.getElementName().trim().length() == 0) ? "?" : inputElement.getElementName(); //$NON-NLS-1$
        }
    }

    private class InputElementTreeSelectionChangedListener implements ISelectionChangedListener {
        @Override
        public void selectionChanged(SelectionChangedEvent event) {
            if (selectedElement != null) {
                selectedElement.dispose();
            }
            if (!(event.getSelection().isEmpty()) && event.getSelection() instanceof IStructuredSelection) {
                IStructuredSelection sel = (IStructuredSelection) event.getSelection();
                CustomXmlInputElement inputElement = (CustomXmlInputElement) sel.getFirstElement();
                selectedElement = new ElementNode(elementContainer, inputElement);
                elementContainer.layout();
                elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                        elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
                container.layout();
                validate();
                updatePreviews();
                removeButton.setEnabled(true);
                addChildButton.setEnabled(true);
                addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addChildElement);
                if (definition.rootInputElement == inputElement) {
                    addNextButton.setEnabled(false);
                } else {
                    addNextButton.setEnabled(true);
                }
                moveUpButton.setEnabled(true);
                moveDownButton.setEnabled(true);
            } else {
                removeButton.setEnabled(false);
                if (definition.rootInputElement == null) {
                    addChildButton.setEnabled(true);
                    addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addDocumentElement);
                } else {
                    addChildButton.setEnabled(false);
                }
                addNextButton.setEnabled(false);
                moveUpButton.setEnabled(false);
                moveDownButton.setEnabled(false);
            }
        }
    }

    @Override
    public void dispose() {
        if (fixedFont != null) {
            fixedFont.dispose();
            fixedFont = null;
        }
        super.dispose();
    }

    private void loadDefinition(CustomXmlTraceDefinition def) {
        categoryText.setText(def.categoryName);
        logtypeText.setText(def.definitionName);
        if (def.timeStampOutputFormat != null) {
            timeStampOutputFormatText.setText(def.timeStampOutputFormat);
        } else {
            timeStampOutputFormatText.setText(""); //$NON-NLS-1$
        }
        treeViewer.setInput(def);

        if (def.rootInputElement != null) {
            treeViewer.setSelection(new StructuredSelection(def.rootInputElement));
        }
    }

    private String getName(CustomXmlInputElement inputElement) {
        String name = (inputElement.getElementName().trim().length() == 0) ? "?" : inputElement.getElementName().trim(); //$NON-NLS-1$
        if (inputElement.getParentElement() == null) {
            return name;
        }
        return getName(inputElement.getParentElement()) + " : " + name; //$NON-NLS-1$
    }

    private String getName(CustomXmlInputAttribute inputAttribute, CustomXmlInputElement inputElement) {
        String name = (inputAttribute.getAttributeName().trim().length() == 0) ? "?" : inputAttribute.getAttributeName().trim(); //$NON-NLS-1$
        return getName(inputElement) + " : " + name; //$NON-NLS-1$
    }

    @Override
    public void setVisible(boolean visible) {
        if (visible) {
            validate();
            updatePreviews();
        }
        super.setVisible(visible);
    }

    /**
     * Get the global list of inputs.
     *
     * @return The list of inputs
     */
    public List<Entry<Tag, String>> getInputs() {
        return getInputs(definition.rootInputElement);
    }

    /**
     * Get the list of inputs for a given element, recursively.
     *
     * @param inputElement
     *            The element
     * @return The list of inputs
     */
    public List<Entry<Tag, String>> getInputs(CustomXmlInputElement inputElement) {
        List<Entry<Tag, String>> inputs = new ArrayList<>();
        if (inputElement.getInputTag() != null && !inputElement.getInputTag().equals(Tag.IGNORE)) {
            Entry<Tag, String> input = new SimpleEntry<>(inputElement.getInputTag(), inputElement.getInputName());
            if (!inputs.contains(input)) {
                inputs.add(input);
            }
        }
        if (inputElement.getAttributes() != null) {
            for (CustomXmlInputAttribute attribute : inputElement.getAttributes()) {
                Entry<Tag, String> input = new SimpleEntry<>(attribute.getInputTag(), attribute.getInputName());
                if (!inputs.contains(input)) {
                    inputs.add(input);
                }
            }
        }
        if (inputElement.getChildElements() != null) {
            for (CustomXmlInputElement childInputElement : inputElement.getChildElements()) {
                for (Entry<Tag, String> input : getInputs(childInputElement)) {
                    if (!inputs.contains(input)) {
                        inputs.add(input);
                    }
                }
            }
        }
        return inputs;
    }

    private void removeElement() {
        selectedElement.dispose();
        selectedElement = null;
        elementContainer.layout();
        elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
        container.layout();
    }

    private String getSelectionText() {
        InputStream inputStream = null;
        if (this.selection instanceof IStructuredSelection) {
            Object sel = ((IStructuredSelection) this.selection).getFirstElement();
            if (sel instanceof IFile) {
                IFile file = (IFile) sel;
                try {
                    inputStream = file.getContents();
                } catch (CoreException e) {
                    return ""; //$NON-NLS-1$
                }
            }
        }
        if (inputStream != null) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));) {
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                    sb.append('\n');
                }
                parseXmlInput(sb.toString());
                return sb.toString();
            } catch (IOException e) {
                return ""; //$NON-NLS-1$
            }
        }
        return ""; //$NON-NLS-1$
    }

    private void parseXmlInput(final String string) {
        try {
            DocumentBuilder db = XmlUtils.newSafeDocumentBuilderFactory().newDocumentBuilder();

            // The following allows xml parsing without access to the dtd
            EntityResolver resolver = (publicId, systemId) -> {
                String empty = ""; //$NON-NLS-1$
                ByteArrayInputStream bais = new ByteArrayInputStream(empty.getBytes());
                return new InputSource(bais);
            };
            db.setEntityResolver(resolver);

            // The following catches xml parsing exceptions
            db.setErrorHandler(new ErrorHandler() {
                @Override
                public void error(SAXParseException saxparseexception) throws SAXException {
                    // Do nothing
                }

                @Override
                public void warning(SAXParseException saxparseexception) throws SAXException {
                    // Do nothing
                }

                @Override
                public void fatalError(SAXParseException saxparseexception) throws SAXException {
                    if (string.trim().length() != 0) {
                        errorText.setText(saxparseexception.getMessage());
                        errorText.setBackground(COLOR_LIGHT_RED);
                        errorText.setVisible(true);
                    }
                    throw saxparseexception;
                }
            });

            errorText.setVisible(false);
            Document doc = null;
            doc = db.parse(new ByteArrayInputStream(string.getBytes()));
            documentElement = doc.getDocumentElement();
        } catch (ParserConfigurationException e) {
            Activator.getDefault().logError("Error pasing XML input string: " + string, e); //$NON-NLS-1$
            documentElement = null;
        } catch (SAXException e) {
            documentElement = null;
        } catch (IOException e) {
            Activator.getDefault().logError("Error pasing XML input string: " + string, e); //$NON-NLS-1$
            documentElement = null;
        }
    }

    private void initValues() {
        timeStampValue = null;
        timeStampFormat = null;
        logEntriesCount = 0;
        logEntryFound = false;
    }

    private void updatePreviews() {
        if (inputText == null) {
            // early update during construction
            return;
        }
        inputText.setStyleRanges(new StyleRange[] {});
        if (selectedElement == null) {
            return;
        }

        initValues();

        selectedElement.updatePreview();

        if (timeStampValue != null && timeStampFormat != null) {
            try {
                TmfTimestampFormat timestampFormat = new TmfTimestampFormat(timeStampFormat);
                long timestamp = timestampFormat.parseValue(timeStampValue);
                if (timeStampOutputFormatText.getText().trim().isEmpty()) {
                    timestampFormat = new TmfTimestampFormat();
                } else {
                    timestampFormat = new TmfTimestampFormat(timeStampOutputFormatText.getText().trim());
                }
                timeStampPreviewText.setText(timestampFormat.format(timestamp));
            } catch (ParseException e) {
                timeStampPreviewText.setText("*parse exception* [" + timeStampValue + "] <> [" + timeStampFormat + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            } catch (IllegalArgumentException e) {
                timeStampPreviewText.setText("*parse exception* [Illegal Argument]"); //$NON-NLS-1$
            }
        } else {
            timeStampPreviewText.setText("*no matching time stamp*"); //$NON-NLS-1$
        }
    }

    private void openHelpShell(String url) {
        if (helpBrowser != null && !helpBrowser.isDisposed()) {
            helpBrowser.getShell().setActive();
            if (!helpBrowser.getUrl().equals(url)) {
                helpBrowser.setUrl(url);
            }
            return;
        }
        final Shell helpShell = new Shell(getShell(), SWT.SHELL_TRIM);
        helpShell.setLayout(new FillLayout());
        helpBrowser = new Browser(helpShell, SWT.NONE);
        helpBrowser.addTitleListener(event -> helpShell.setText(event.title));
        Rectangle r = container.getBounds();
        Point p = container.toDisplay(r.x, r.y);
        Rectangle trim = helpShell.computeTrim(p.x + (r.width - 750) / 2, p.y + (r.height - 400) / 2, 750, 400);
        helpShell.setBounds(trim);
        helpShell.open();
        helpBrowser.setUrl(url);
    }

    private class UpdateListener implements ModifyListener, SelectionListener {

        @Override
        public void modifyText(ModifyEvent e) {
            validate();
            updatePreviews();
        }

        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            validate();
            updatePreviews();
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            validate();
            updatePreviews();
        }

    }

    private class ElementNode {
        private final CustomXmlInputElement inputElement;
        private final Group group;
        private List<Attribute> attributes = new ArrayList<>();
        private List<ElementNode> childElements = new ArrayList<>();
        private Text elementNameText;
        private Composite tagComposite;
        private Combo tagCombo;
        private Label tagLabel;
        private Text tagText;
        private Combo actionCombo;
        private Label previewLabel;
        private Text previewText;
        private Button logEntryButton;
        private Button eventTypeButton;
        private Text eventTypeText;
        private Label fillerLabel;
        private Composite addAttributeComposite;
        private Button addAttributeButton;
        private Label addAttributeLabel;

        public ElementNode(Composite parent, CustomXmlInputElement inputElement) {
            this.inputElement = inputElement;

            group = new Group(parent, SWT.NONE);
            GridLayout gl = new GridLayout(2, false);
            gl.marginHeight = 0;
            group.setLayout(gl);
            group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            group.setText(getName(inputElement));

            Label label = new Label(group, SWT.NULL);
            label.setText(Messages.CustomXmlParserInputWizardPage_elementName);
            label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

            elementNameText = new Text(group, SWT.BORDER | SWT.SINGLE);
            GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
            gd.widthHint = 0;
            elementNameText.setLayoutData(gd);
            elementNameText.addModifyListener(e -> {
                ElementNode.this.inputElement.setElementName(elementNameText.getText().trim());
                group.setText(getName(ElementNode.this.inputElement));
            });
            elementNameText.setText(inputElement.getElementName());
            elementNameText.addModifyListener(updateListener);

            if (inputElement.getParentElement() != null) {
                previewLabel = new Label(group, SWT.NULL);
                previewLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
                previewLabel.setText(Messages.CustomXmlParserInputWizardPage_preview);

                previewText = new Text(group, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
                gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
                gd.widthHint = 0;
                previewText.setLayoutData(gd);
                previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingElement);
                previewText.setBackground(COLOR_WIDGET_BACKGROUND);

                logEntryButton = new Button(group, SWT.CHECK);
                logEntryButton.setText(Messages.CustomXmlParserInputWizardPage_logEntry);
                logEntryButton.setSelection(inputElement.isLogEntry());
                logEntryButton.addSelectionListener(new SelectionListener() {
                    @Override
                    public void widgetDefaultSelected(SelectionEvent e) {
                        // Do nothing
                    }

                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        CustomXmlInputElement parentElem = ElementNode.this.inputElement.getParentElement();
                        while (parentElem != null) {
                            parentElem.setLogEntry(false);
                            parentElem = parentElem.getParentElement();
                        }
                    }
                });
                logEntryButton.addSelectionListener(updateListener);

                tagComposite = new Composite(group, SWT.FILL);
                GridLayout tagLayout = new GridLayout(4, false);
                tagLayout.marginWidth = 0;
                tagLayout.marginHeight = 0;
                tagComposite.setLayout(tagLayout);
                tagComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

                tagCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
                tagCombo.setItems(new String[] {
                        Tag.IGNORE.toString(),
                        Tag.TIMESTAMP.toString(),
                        Tag.EVENT_TYPE.toString(),
                        Tag.MESSAGE.toString(),
                        Tag.EXTRA_FIELD_NAME.toString(),
                        Tag.EXTRA_FIELD_VALUE.toString(),
                        Tag.OTHER.toString() });
                tagCombo.setVisibleItemCount(tagCombo.getItemCount());
                tagCombo.addSelectionListener(new SelectionListener() {
                    @Override
                    public void widgetDefaultSelected(SelectionEvent e) {
                        // Do nothing
                    }

                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        tagText.removeModifyListener(updateListener);
                        switch (tagCombo.getSelectionIndex()) {
                        case 0: // Ignore
                            tagLabel.setVisible(false);
                            tagText.setVisible(false);
                            actionCombo.setVisible(false);
                            break;
                        case 1: // Time Stamp
                            tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                            tagLabel.setVisible(true);
                            tagText.setVisible(true);
                            tagText.addModifyListener(updateListener);
                            actionCombo.setVisible(true);
                            break;
                        case 2: // Event type
                            tagLabel.setVisible(false);
                            tagText.setVisible(false);
                            actionCombo.setVisible(true);
                            break;
                        case 3: // Message
                            tagLabel.setVisible(false);
                            tagText.setVisible(false);
                            actionCombo.setVisible(true);
                            break;
                        case 4: // Field names
                            tagLabel.setVisible(false);
                            tagText.setVisible(false);
                            actionCombo.setVisible(false);
                            break;
                        case 5: // Field values
                            tagLabel.setVisible(false);
                            tagText.setVisible(false);
                            actionCombo.setVisible(true);
                            break;
                        case 6: // Other
                            tagLabel.setText(Messages.CustomXmlParserInputWizardPage_name);
                            tagLabel.setVisible(true);
                            if (tagText.getText().trim().length() == 0) {
                                tagText.setText(elementNameText.getText().trim());
                            }
                            tagText.setVisible(true);
                            tagText.addModifyListener(updateListener);
                            actionCombo.setVisible(true);
                            break;
                        default:
                            break;
                        }
                        tagComposite.layout();
                        validate();
                        updatePreviews();
                    }
                });

                tagLabel = new Label(tagComposite, SWT.NULL);
                tagLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

                tagText = new Text(tagComposite, SWT.BORDER | SWT.SINGLE);
                gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
                gd.widthHint = 0;
                tagText.setLayoutData(gd);

                actionCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
                actionCombo.setItems(new String[] { Messages.CustomXmlParserInputWizardPage_set, Messages.CustomXmlParserInputWizardPage_append,
                        Messages.CustomXmlParserInputWizardPage_appendWith });
                actionCombo.select(inputElement.getInputAction());
                actionCombo.addSelectionListener(updateListener);

                if (inputElement.getInputTag().equals(Tag.IGNORE)) {
                    tagCombo.select(0);
                    tagLabel.setVisible(false);
                    tagText.setVisible(false);
                    actionCombo.setVisible(false);
                } else if (inputElement.getInputTag().equals(Tag.TIMESTAMP)) {
                    tagCombo.select(1);
                    tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                    tagText.setText(inputElement.getInputFormat());
                    tagText.addModifyListener(updateListener);
                    actionCombo.setVisible(true);
                } else if (inputElement.getInputTag().equals(Tag.EVENT_TYPE)) {
                    tagCombo.select(2);
                    tagLabel.setVisible(false);
                    tagText.setVisible(false);
                    actionCombo.setVisible(true);
                } else if (inputElement.getInputTag().equals(Tag.MESSAGE)) {
                    tagCombo.select(3);
                    tagLabel.setVisible(false);
                    tagText.setVisible(false);
                    actionCombo.setVisible(true);
                } else if (inputElement.getInputTag().equals(Tag.EXTRA_FIELD_NAME)) {
                    tagCombo.select(4);
                    tagLabel.setVisible(false);
                    tagText.setVisible(false);
                    actionCombo.setVisible(false);
                } else if (inputElement.getInputTag().equals(Tag.EXTRA_FIELD_VALUE)) {
                    tagCombo.select(5);
                    tagLabel.setVisible(false);
                    tagText.setVisible(false);
                    actionCombo.setVisible(true);
                } else {
                    tagCombo.select(6);
                    tagLabel.setText(Messages.CustomXmlParserInputWizardPage_name);
                    tagText.setText(inputElement.getInputName());
                    tagText.addModifyListener(updateListener);
                    actionCombo.setVisible(true);
                }

                eventTypeButton = new Button(group, SWT.CHECK);
                eventTypeButton.setText(Messages.CustomTxtParserInputWizardPage_eventType);
                eventTypeButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
                eventTypeButton.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        if (eventTypeButton.getSelection()) {
                            eventTypeText.setEnabled(true);
                        } else {
                            eventTypeText.setEnabled(false);
                        }
                    }
                });
                eventTypeButton.addSelectionListener(updateListener);

                eventTypeText = new Text(group, SWT.BORDER | SWT.SINGLE);
                gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
                gd.widthHint = 0;
                eventTypeText.setLayoutData(gd);
                if (inputElement.getEventType() != null) {
                    eventTypeText.setText(inputElement.getEventType());
                    eventTypeButton.setSelection(true);
                } else {
                    eventTypeText.setEnabled(false);
                    eventTypeButton.setSelection(false);
                }
                eventTypeText.addModifyListener(updateListener);
            }

            if (inputElement.getAttributes() != null) {
                for (CustomXmlInputAttribute inputAttribute : inputElement.getAttributes()) {
                    Attribute attribute = new Attribute(group, this, inputAttribute, attributes.size() + 1);
                    attributes.add(attribute);
                }
            }

            createAddButton();
        }

        private void updatePreview() {
            Element element = getPreviewElement(inputElement);
            // no preview text for document element
            if (inputElement.getParentElement() != null) {
                previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingElement);
                if (element != null) {
                    previewText.setText(CustomXmlTrace.parseElement(element, new StringBuffer()).toString());
                    if (logEntryButton.getSelection()) {
                        if (!logEntryFound) {
                            logEntryFound = true;
                            logEntriesCount++;
                        } else {
                            // remove nested log entry
                            logEntryButton.setSelection(false);
                        }
                    }
                    if (tagCombo.getText().equals(Tag.TIMESTAMP.toString()) && logEntriesCount <= 1) {
                        String value = previewText.getText().trim();
                        if (value.length() != 0) {
                            if (actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_SET) {
                                timeStampValue = value;
                                timeStampFormat = tagText.getText().trim();
                            } else if (actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND) {
                                if (timeStampValue != null) {
                                    timeStampValue += value;
                                    timeStampFormat += tagText.getText().trim();
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = tagText.getText().trim();
                                }
                            } else if (actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND_WITH_SEPARATOR) {
                                if (timeStampValue != null) {
                                    timeStampValue += CustomTraceDefinition.SEPARATOR + value;
                                    timeStampFormat += CustomTraceDefinition.SEPARATOR + tagText.getText().trim();
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = tagText.getText().trim();
                                }
                            }
                        }
                    }
                }
            }
            for (Attribute attribute : attributes) {
                if (element != null) {
                    String value = element.getAttribute(attribute.attributeNameText.getText().trim());
                    if (value.length() != 0) {
                        attribute.previewText.setText(value);
                        if (attribute.tagCombo.getText().equals(Tag.TIMESTAMP.toString()) && logEntriesCount <= 1) {
                            if (attribute.actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_SET) {
                                timeStampValue = value;
                                timeStampFormat = attribute.tagText.getText().trim();
                            } else if (attribute.actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND) {
                                if (timeStampValue != null) {
                                    timeStampValue += value;
                                    timeStampFormat += attribute.tagText.getText().trim();
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = attribute.tagText.getText().trim();
                                }
                            } else if (attribute.actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND_WITH_SEPARATOR) {
                                if (timeStampValue != null) {
                                    timeStampValue += " | " + value; //$NON-NLS-1$
                                    timeStampFormat += " | " + attribute.tagText.getText().trim(); //$NON-NLS-1$
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = attribute.tagText.getText().trim();
                                }
                            }
                        }
                    } else {
                        attribute.previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingAttribute);
                    }
                } else {
                    attribute.previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingElement);
                }
            }
            for (ElementNode child : childElements) {
                child.updatePreview();
            }
            if (logEntryButton != null && logEntryButton.getSelection()) {
                logEntryFound = false;
            }
        }

        private void createAddButton() {
            fillerLabel = new Label(group, SWT.NONE);

            addAttributeComposite = new Composite(group, SWT.NONE);
            addAttributeComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            GridLayout addAttributeLayout = new GridLayout(2, false);
            addAttributeLayout.marginHeight = 0;
            addAttributeLayout.marginWidth = 0;
            addAttributeComposite.setLayout(addAttributeLayout);

            addAttributeButton = new Button(addAttributeComposite, SWT.PUSH);
            addAttributeButton.setImage(ADD_IMAGE);
            addAttributeButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addAttribute);
            addAttributeButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    removeAddButton();
                    String attributeName = getAttributeNameSuggestion(inputElement);
                    CustomXmlInputAttribute inputAttribute = new CustomXmlInputAttribute(attributeName, Tag.OTHER, attributeName, 0, ""); //$NON-NLS-1$
                    attributes.add(new Attribute(group, ElementNode.this, inputAttribute, attributes.size() + 1));
                    createAddButton();
                    elementContainer.layout();
                    elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                            elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
                    group.getParent().layout();
                    validate();
                    updatePreviews();
                }
            });

            addAttributeLabel = new Label(addAttributeComposite, SWT.NULL);
            addAttributeLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            addAttributeLabel.setText(Messages.CustomXmlParserInputWizardPage_newAttibute);
        }

        private void removeAddButton() {
            fillerLabel.dispose();
            addAttributeComposite.dispose();
        }

        private void removeAttribute(int attributeNumber) {
            int nb = attributeNumber;
            if (--nb < attributes.size()) {
                attributes.remove(nb).dispose();
                for (int i = nb; i < attributes.size(); i++) {
                    attributes.get(i).setAttributeNumber(i + 1);
                }
                elementContainer.layout();
                elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                        elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
                group.getParent().layout();
            }
        }

        private void dispose() {
            group.dispose();
        }

        private void extractInputs() {
            inputElement.setElementName(elementNameText.getText().trim());
            if (inputElement.getParentElement() != null) {
                inputElement.setLogEntry(logEntryButton.getSelection());
                inputElement.setEventType(eventTypeButton.getSelection() ? eventTypeText.getText().trim() : null);
                Tag inputTag = Tag.fromLabel(tagCombo.getText());
                inputElement.setInputTag(inputTag);
                if (inputTag.equals(Tag.OTHER)) {
                    inputElement.setInputName(tagText.getText().trim());
                } else {
                    inputElement.setInputName(inputTag.toString());
                    if (inputTag.equals(Tag.TIMESTAMP)) {
                        inputElement.setInputFormat(tagText.getText().trim());
                    }
                }
                inputElement.setInputAction(actionCombo.getSelectionIndex());
            }
            inputElement.setAttributes(new ArrayList<CustomXmlInputAttribute>(attributes.size()));
            for (int i = 0; i < attributes.size(); i++) {
                String inputName = null;
                String inputFormat = null;
                Attribute attribute = attributes.get(i);
                String attributeName = attribute.attributeNameText.getText().trim();
                Tag inputTag = Tag.fromLabel(attribute.tagCombo.getText());
                if (inputTag.equals(Tag.OTHER)) {
                    inputName = attribute.tagText.getText().trim();
                } else {
                    inputName = inputTag.toString();
                    if (inputTag.equals(Tag.TIMESTAMP)) {
                        inputFormat = attribute.tagText.getText().trim();
                    }
                }
                int inputAction = attribute.actionCombo.getSelectionIndex();
                inputElement.addAttribute(new CustomXmlInputAttribute(attributeName, inputTag, inputName, inputAction, inputFormat));
            }
        }
    }

    private class Attribute {
        private ElementNode element;
        private int attributeNumber;

        // children of parent (must be disposed)
        private Composite labelComposite;
        private Composite attributeComposite;
        private Label filler;
        private Composite tagComposite;

        // children of labelComposite
        private Label attributeLabel;

        // children of attributeComposite
        private Text attributeNameText;
        private Text previewText;

        // children of tagComposite
        private Combo tagCombo;
        private Label tagLabel;
        private Text tagText;
        private Combo actionCombo;

        public Attribute(Composite parent, ElementNode element, CustomXmlInputAttribute inputAttribute, int attributeNumber) {
            this.element = element;
            this.attributeNumber = attributeNumber;

            labelComposite = new Composite(parent, SWT.FILL);
            GridLayout labelLayout = new GridLayout(2, false);
            labelLayout.marginWidth = 0;
            labelLayout.marginHeight = 0;
            labelComposite.setLayout(labelLayout);
            labelComposite.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

            Button deleteButton = new Button(labelComposite, SWT.PUSH);
            deleteButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
            deleteButton.setImage(DELETE_IMAGE);
            deleteButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_removeAttribute);
            deleteButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    Attribute.this.element.removeAttribute(Attribute.this.attributeNumber);
                    validate();
                    updatePreviews();
                }
            });

            attributeLabel = new Label(labelComposite, SWT.NULL);
            attributeLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
            attributeLabel.setText(Messages.CustomXmlParserInputWizardPage_attibute);

            attributeComposite = new Composite(parent, SWT.FILL);
            GridLayout attributeLayout = new GridLayout(4, false);
            attributeLayout.marginWidth = 0;
            attributeLayout.marginHeight = 0;
            attributeComposite.setLayout(attributeLayout);
            attributeComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

            Label nameLabel = new Label(attributeComposite, SWT.NONE);
            nameLabel.setText(Messages.CustomXmlParserInputWizardPage_name);

            attributeNameText = new Text(attributeComposite, SWT.BORDER | SWT.SINGLE);
            attributeNameText.setLayoutData(new GridData(120, SWT.DEFAULT));
            attributeNameText.setText(inputAttribute.getAttributeName());
            attributeNameText.addModifyListener(updateListener);

            Label previewLabel = new Label(attributeComposite, SWT.NONE);
            previewLabel.setText(Messages.CustomXmlParserInputWizardPage_preview);

            previewText = new Text(attributeComposite, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
            GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
            gd.widthHint = 0;
            previewText.setLayoutData(gd);
            previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatch);
            previewText.setBackground(COLOR_WIDGET_BACKGROUND);

            filler = new Label(parent, SWT.NULL);

            tagComposite = new Composite(parent, SWT.FILL);
            GridLayout tagLayout = new GridLayout(4, false);
            tagLayout.marginWidth = 0;
            tagLayout.marginHeight = 0;
            tagComposite.setLayout(tagLayout);
            tagComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

            tagCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
            tagCombo.setItems(new String[] {
                    Tag.TIMESTAMP.toString(),
                    Tag.EVENT_TYPE.toString(),
                    Tag.MESSAGE.toString(),
                    Tag.EXTRA_FIELD_NAME.toString(),
                    Tag.EXTRA_FIELD_VALUE.toString(),
                    Tag.OTHER.toString() });
            tagCombo.select(3); // Other
            tagCombo.addSelectionListener(new SelectionListener() {
                @Override
                public void widgetDefaultSelected(SelectionEvent e) {
                    // Do nothing
                }

                @Override
                public void widgetSelected(SelectionEvent e) {
                    tagText.removeModifyListener(updateListener);
                    switch (tagCombo.getSelectionIndex()) {
                    case 0: // Time Stamp
                        tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                        tagLabel.setVisible(true);
                        tagText.setVisible(true);
                        tagText.addModifyListener(updateListener);
                        actionCombo.setVisible(true);
                        break;
                    case 1: // Event type
                        tagLabel.setVisible(false);
                        tagText.setVisible(false);
                        actionCombo.setVisible(true);
                        break;
                    case 2: // Message
                        tagLabel.setVisible(false);
                        tagText.setVisible(false);
                        actionCombo.setVisible(true);
                        break;
                    case 3: // Field names
                        tagLabel.setVisible(false);
                        tagText.setVisible(false);
                        actionCombo.setVisible(false);
                        break;
                    case 4: // Field values
                        tagLabel.setVisible(false);
                        tagText.setVisible(false);
                        actionCombo.setVisible(true);
                        break;
                    case 5: // Other
                        tagLabel.setText(Messages.CustomXmlParserInputWizardPage_name);
                        tagLabel.setVisible(true);
                        if (tagText.getText().trim().length() == 0) {
                            tagText.setText(attributeNameText.getText().trim());
                        }
                        tagText.setVisible(true);
                        tagText.addModifyListener(updateListener);
                        actionCombo.setVisible(true);
                        break;
                    default:
                        break;
                    }
                    tagComposite.layout();
                    validate();
                    updatePreviews();
                }
            });

            tagLabel = new Label(tagComposite, SWT.NULL);
            tagLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

            tagText = new Text(tagComposite, SWT.BORDER | SWT.SINGLE);
            gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
            gd.widthHint = 0;
            tagText.setLayoutData(gd);
            tagText.setText(attributeNameText.getText());

            actionCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
            actionCombo.setItems(new String[] { Messages.CustomXmlParserInputWizardPage_set, Messages.CustomXmlParserInputWizardPage_append,
                    Messages.CustomXmlParserInputWizardPage_appendWith });
            actionCombo.select(inputAttribute.getInputAction());
            actionCombo.addSelectionListener(updateListener);

            if (inputAttribute.getInputTag().equals(Tag.TIMESTAMP)) {
                tagCombo.select(0);
                tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                tagText.setText(inputAttribute.getInputFormat());
                tagText.addModifyListener(updateListener);
                actionCombo.setVisible(true);
            } else if (inputAttribute.getInputTag().equals(Tag.EVENT_TYPE)) {
                tagCombo.select(1);
                tagLabel.setVisible(false);
                tagText.setVisible(false);
                actionCombo.setVisible(true);
            } else if (inputAttribute.getInputTag().equals(Tag.MESSAGE)) {
                tagCombo.select(2);
                tagLabel.setVisible(false);
                tagText.setVisible(false);
                actionCombo.setVisible(true);
            } else if (inputAttribute.getInputTag().equals(Tag.EXTRA_FIELD_NAME)) {
                tagCombo.select(3);
                tagLabel.setVisible(false);
                tagText.setVisible(false);
                actionCombo.setVisible(false);
            } else if (inputAttribute.getInputTag().equals(Tag.EXTRA_FIELD_VALUE)) {
                tagCombo.select(4);
                tagLabel.setVisible(false);
                tagText.setVisible(false);
                actionCombo.setVisible(true);
            } else {
                tagCombo.select(5);
                tagLabel.setText(Messages.CustomXmlParserInputWizardPage_name);
                tagText.setText(inputAttribute.getInputName());
                tagText.addModifyListener(updateListener);
                actionCombo.setVisible(true);
            }
        }

        private void dispose() {
            labelComposite.dispose();
            attributeComposite.dispose();
            filler.dispose();
            tagComposite.dispose();
        }

        private void setAttributeNumber(int attributeNumber) {
            this.attributeNumber = attributeNumber;
            labelComposite.layout();
        }
    }

    private Element getPreviewElement(CustomXmlInputElement inputElement) {
        CustomXmlInputElement currentElement = inputElement;
        Element element = documentElement;
        if (element != null) {
            if (!documentElement.getNodeName().equals(definition.rootInputElement.getElementName())) {
                return null;
            }
            ArrayList<String> elementNames = new ArrayList<>();
            while (currentElement != null) {
                elementNames.add(currentElement.getElementName());
                currentElement = currentElement.getParentElement();
            }
            for (int i = elementNames.size() - 1; --i >= 0;) {
                NodeList childList = element.getChildNodes();
                element = null;
                for (int j = 0; j < childList.getLength(); j++) {
                    Node child = childList.item(j);
                    if (child instanceof Element && child.getNodeName().equals(elementNames.get(i))) {
                        element = (Element) child;
                        break;
                    }
                }
                if (element == null) {
                    break;
                }
            }
            if (element != null) {
                return element;
            }
        }
        return null;
    }

    private String getChildNameSuggestion(CustomXmlInputElement inputElement) {
        if (inputElement == null) {
            if (documentElement != null) {
                return documentElement.getNodeName();
            }
        } else {
            Element element = getPreviewElement(inputElement);
            if (element != null) {
                NodeList childNodes = element.getChildNodes();
                for (int i = 0; i < childNodes.getLength(); i++) {
                    Node node = childNodes.item(i);
                    if (node instanceof Element) {
                        boolean unused = true;
                        if (inputElement.getChildElements() != null) {
                            for (CustomXmlInputElement child : inputElement.getChildElements()) {
                                if (child.getElementName().equals(node.getNodeName())) {
                                    unused = false;
                                    break;
                                }
                            }
                        }
                        if (unused) {
                            return node.getNodeName();
                        }
                    }
                }
            }
        }
        return ""; //$NON-NLS-1$
    }

    private String getAttributeNameSuggestion(CustomXmlInputElement inputElement) {
        Element element = getPreviewElement(inputElement);
        if (element != null) {
            NamedNodeMap attributeMap = element.getAttributes();
            for (int i = 0; i < attributeMap.getLength(); i++) {
                Node node = attributeMap.item(i);
                boolean unused = true;
                if (inputElement.getAttributes() != null) {
                    for (CustomXmlInputAttribute attribute : inputElement.getAttributes()) {
                        if (attribute.getAttributeName().equals(node.getNodeName())) {
                            unused = false;
                            break;
                        }
                    }
                }
                if (unused) {
                    return node.getNodeName();
                }
            }
        }
        return ""; //$NON-NLS-1$
    }

    private void validate() {
        definition.categoryName = categoryText.getText().trim();
        definition.definitionName = logtypeText.getText().trim();
        definition.timeStampOutputFormat = timeStampOutputFormatText.getText().trim();

        if (selectedElement != null) {
            selectedElement.extractInputs();
            treeViewer.refresh();
        }

        List<String> errors = new ArrayList<>();

        if (definition.categoryName.length() == 0) {
            errors.add(Messages.CustomXmlParserInputWizardPage_emptyCategoryError);
            categoryText.setBackground(COLOR_LIGHT_RED);
        } else if (definition.definitionName.length() == 0) {
            errors.add(Messages.CustomXmlParserInputWizardPage_emptyLogTypeError);
            logtypeText.setBackground(COLOR_LIGHT_RED);
        } else {
            categoryText.setBackground(COLOR_TEXT_BACKGROUND);
            logtypeText.setBackground(COLOR_TEXT_BACKGROUND);
            if (definition.categoryName.indexOf(':') != -1) {
                errors.add(Messages.CustomXmlParserInputWizardPage_invalidCategoryError);
                categoryText.setBackground(COLOR_LIGHT_RED);
            }
            if (definition.definitionName.indexOf(':') != -1) {
                errors.add(Messages.CustomXmlParserInputWizardPage_invalidLogTypeError);
                logtypeText.setBackground(COLOR_LIGHT_RED);
            }
            for (TraceTypeHelper helper : TmfTraceType.getTraceTypeHelpers()) {
                if (definition.categoryName.equals(helper.getCategoryName()) &&
                        definition.definitionName.equals(helper.getName()) &&
                        (editDefinitionName == null || !editDefinitionName.equals(definition.definitionName)) &&
                        (editCategoryName == null || !editCategoryName.equals(definition.categoryName))) {
                    errors.add(Messages.CustomXmlParserInputWizardPage_duplicatelogTypeError);
                    logtypeText.setBackground(COLOR_LIGHT_RED);
                    break;
                }
            }
        }

        if (definition.rootInputElement == null) {
            errors.add(Messages.CustomXmlParserInputWizardPage_noDocumentError);
        }

        if (definition.rootInputElement != null) {
            logEntryFound = false;
            timeStampFound = false;

            errors.addAll(validateElement(definition.rootInputElement));

            if ((definition.rootInputElement.getAttributes() != null && !definition.rootInputElement.getAttributes().isEmpty())
                    || (definition.rootInputElement.getChildElements() != null && !definition.rootInputElement.getChildElements().isEmpty())
                    || errors.isEmpty()) {
                if (!logEntryFound) {
                    errors.add(Messages.CustomXmlParserInputWizardPage_missingLogEntryError);
                }

                if (timeStampFound && !definition.timeStampOutputFormat.isEmpty()) {
                    try {
                        new TmfTimestampFormat(timeStampOutputFormatText.getText().trim());
                        timeStampOutputFormatText.setBackground(COLOR_TEXT_BACKGROUND);
                    } catch (IllegalArgumentException e) {
                        errors.add(Messages.CustomXmlParserInputWizardPage_elementInvalidTimestampFmtError);
                        timeStampOutputFormatText.setBackground(COLOR_LIGHT_RED);
                    }
                } else {
                    timeStampOutputFormatText.setBackground(COLOR_TEXT_BACKGROUND);
                    if (!timeStampFound) {
                        timeStampPreviewText.setText(Messages.CustomXmlParserInputWizardPage_noTimestampElementOrAttribute);
                    }
                }
            }
        } else {
            timeStampPreviewText.setText(Messages.CustomXmlParserInputWizardPage_noTimestampElementOrAttribute);
        }

        if (errors.isEmpty()) {
            setDescription(defaultDescription);
            setPageComplete(true);
        } else {
            setDescription(Joiner.on(' ').join(errors));
            setPageComplete(false);
        }
    }

    /**
     * Clean up the specified XML element.
     *
     * @param inputElement
     *            The element to clean up
     * @return The validated element
     */
    public List<String> validateElement(CustomXmlInputElement inputElement) {
        List<String> errors = new ArrayList<>();
        ElementNode elementNode = null;
        if (selectedElement != null && selectedElement.inputElement.equals(inputElement)) {
            elementNode = selectedElement;
        }
        if (inputElement == definition.rootInputElement) {
            if (inputElement.getElementName().length() == 0) {
                errors.add(Messages.CustomXmlParserInputWizardPage_missingDocumentElementError);
                if (elementNode != null) {
                    elementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                }
            } else {
                if (elementNode != null) {
                    elementNode.elementNameText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
        }
        if (inputElement != definition.rootInputElement) {
            if (inputElement.isLogEntry()) {
                logEntryFound = true;
            }
            if (inputElement.getInputTag().equals(Tag.TIMESTAMP)) {
                timeStampFound = true;
                if (inputElement.getInputFormat().length() == 0) {
                    errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_elementMissingTimestampFmtError, getName(inputElement)));
                    if (elementNode != null) {
                        elementNode.tagText.setBackground(COLOR_LIGHT_RED);
                    }
                } else {
                    try {
                        new TmfTimestampFormat(inputElement.getInputFormat());
                        if (elementNode != null) {
                            elementNode.tagText.setBackground(COLOR_TEXT_BACKGROUND);
                        }
                    } catch (IllegalArgumentException e) {
                        errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_elementInvalidTimestampFmtError, getName(inputElement)));
                        if (elementNode != null) {
                            elementNode.tagText.setBackground(COLOR_LIGHT_RED);
                        }
                    }
                }
            } else if (inputElement.getInputName().length() == 0) {
                errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_elementMissingInputNameError, getName(inputElement)));
                if (elementNode != null) {
                    elementNode.tagText.setBackground(COLOR_LIGHT_RED);
                }
            } else if (inputElement.getInputTag().equals(Tag.OTHER) && Tag.fromLabel(inputElement.getInputName()) != null) {
                errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_elementReservedInputNameError, getName(inputElement)));
                if (elementNode != null) {
                    elementNode.tagText.setBackground(COLOR_LIGHT_RED);
                }
            } else {
                if (elementNode != null) {
                    elementNode.tagText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
            if (inputElement.getEventType() != null && inputElement.getEventType().trim().isEmpty()) {
                errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_emptyEventTypeError, getName(inputElement)));
                if (elementNode != null) {
                    elementNode.eventTypeText.setBackground(COLOR_LIGHT_RED);
                }
            } else {
                if (elementNode != null) {
                    elementNode.eventTypeText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
        }
        if (inputElement.getAttributes() != null) {
            if (elementNode != null) {
                for (Attribute attribute : elementNode.attributes) {
                    attribute.attributeNameText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
            for (int i = 0; i < inputElement.getAttributes().size(); i++) {
                CustomXmlInputAttribute attribute = inputElement.getAttributes().get(i);
                boolean duplicate = false;
                for (int j = i + 1; j < inputElement.getAttributes().size(); j++) {
                    CustomXmlInputAttribute otherAttribute = inputElement.getAttributes().get(j);
                    if (otherAttribute.getAttributeName().equals(attribute.getAttributeName())) {
                        duplicate = true;
                        if (elementNode != null) {
                            elementNode.attributes.get(j).attributeNameText.setBackground(COLOR_LIGHT_RED);
                        }
                    }
                }
                if (attribute.getAttributeName().length() == 0) {
                    errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_attributeMissingNameError, getName(inputElement)));
                    if (elementNode != null) {
                        elementNode.attributes.get(i).attributeNameText.setBackground(COLOR_LIGHT_RED);
                    }
                } else if (duplicate) {
                    errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_attributeDuplicateNameError, getName(attribute, inputElement)));
                    if (elementNode != null) {
                        elementNode.attributes.get(i).attributeNameText.setBackground(COLOR_LIGHT_RED);
                    }
                }
                if (attribute.getInputTag().equals(Tag.TIMESTAMP)) {
                    timeStampFound = true;
                    if (attribute.getInputFormat().length() == 0) {
                        errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_attributeMissingTimestampFmtError, getName(attribute, inputElement)));
                        if (elementNode != null) {
                            elementNode.attributes.get(i).tagText.setBackground(COLOR_LIGHT_RED);
                        }
                    } else {
                        try {
                            new TmfTimestampFormat(attribute.getInputFormat());
                            if (elementNode != null) {
                                elementNode.attributes.get(i).tagText.setBackground(COLOR_TEXT_BACKGROUND);
                            }
                        } catch (IllegalArgumentException e) {
                            errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_attributeInvalidTimestampFmtError, getName(attribute, inputElement)));
                            if (elementNode != null) {
                                elementNode.attributes.get(i).tagText.setBackground(COLOR_LIGHT_RED);
                            }
                        }
                    }
                } else if (attribute.getInputName().length() == 0) {
                    errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_attributeMissingInputNameError, getName(attribute, inputElement)));
                    if (elementNode != null) {
                        elementNode.attributes.get(i).tagText.setBackground(COLOR_LIGHT_RED);
                    }
                } else if (attribute.getInputTag().equals(Tag.OTHER) && Tag.fromLabel(attribute.getInputName()) != null) {
                    errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_attributeReservedInputNameError, getName(attribute, inputElement)));
                    if (elementNode != null) {
                        elementNode.attributes.get(i).tagText.setBackground(COLOR_LIGHT_RED);
                    }
                } else {
                    if (elementNode != null) {
                        elementNode.attributes.get(i).tagText.setBackground(COLOR_TEXT_BACKGROUND);
                    }
                }
            }
        }
        if (inputElement.getChildElements() != null) {
            for (CustomXmlInputElement child : inputElement.getChildElements()) {
                ElementNode childElementNode = null;
                if (selectedElement != null && selectedElement.inputElement.equals(child)) {
                    childElementNode = selectedElement;
                }
                if (childElementNode != null) {
                    childElementNode.elementNameText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
            for (int i = 0; i < inputElement.getChildElements().size(); i++) {
                CustomXmlInputElement child = inputElement.getChildElements().get(i);
                ElementNode childElementNode = null;
                if (selectedElement != null && selectedElement.inputElement.equals(child)) {
                    childElementNode = selectedElement;
                }
                if (child.getElementName().length() == 0) {
                    errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_elementMissingNameError, getName(child)));
                    if (childElementNode != null) {
                        childElementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                    }
                } else {
                    boolean duplicate = false;
                    for (int j = i + 1; j < inputElement.getChildElements().size(); j++) {
                        CustomXmlInputElement otherChild = inputElement.getChildElements().get(j);
                        if (otherChild.getElementName().equals(child.getElementName())) {
                            duplicate = true;
                            ElementNode otherChildElementNode = null;
                            if (selectedElement != null && selectedElement.inputElement.equals(otherChild)) {
                                otherChildElementNode = selectedElement;
                            }
                            if (otherChildElementNode != null) {
                                otherChildElementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                            }
                        }
                    }
                    if (duplicate) {
                        errors.add(NLS.bind(Messages.CustomXmlParserInputWizardPage_elementDuplicateNameError, getName(child)));
                        if (childElementNode != null) {
                            childElementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                        }
                    }
                }

                errors.addAll(validateElement(child));
            }
        }
        return errors;
    }

    /**
     * Get the trace definition.
     *
     * @return The trace definition
     */
    public CustomXmlTraceDefinition getDefinition() {
        return definition;
    }

    /**
     * Get the raw text input.
     *
     * @return The raw text input.
     */
    public char[] getInputText() {
        return inputText.getText().toCharArray();
    }
}
