001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.xml.content;
029
030import org.opencms.file.CmsObject;
031import org.opencms.main.CmsLog;
032import org.opencms.main.OpenCms;
033import org.opencms.xml.I_CmsXmlDocument;
034import org.opencms.xml.types.I_CmsXmlContentValue;
035
036import java.util.ArrayList;
037import java.util.List;
038import java.util.Locale;
039
040import javax.xml.parsers.DocumentBuilder;
041import javax.xml.parsers.DocumentBuilderFactory;
042import javax.xml.parsers.ParserConfigurationException;
043
044import org.apache.commons.logging.Log;
045
046import org.dom4j.DocumentException;
047import org.dom4j.io.DOMReader;
048import org.dom4j.io.DOMWriter;
049import org.w3c.dom.Document;
050import org.w3c.dom.Element;
051import org.w3c.dom.Node;
052import org.w3c.dom.NodeList;
053
054/**
055 * Provides extension functions for use in XSLT version transformation files.
056 *
057 * <p>An instance of this class is meant to be used for only a single content conversion.
058 */
059public class CmsXsltContext {
060
061    /** The logger instance for this class. */
062    private static final Log LOG = CmsLog.getLog(CmsXsltContext.class);
063
064    /** The CMS context. */
065    private CmsObject m_cms;
066
067    /** The document builder factory. */
068    private DocumentBuilderFactory m_documentBuilderFactory;
069
070    /** The document builder. */
071    private DocumentBuilder m_documentBuilder;
072
073    /**
074     * Creates a new instance.
075     *
076     * @param cms the CMS context
077     */
078    public CmsXsltContext(CmsObject cms) {
079
080        m_cms = cms;
081        m_documentBuilderFactory = DocumentBuilderFactory.newInstance();
082        try {
083            m_documentBuilder = m_documentBuilderFactory.newDocumentBuilder();
084        } catch (ParserConfigurationException e) {
085            throw new RuntimeException(e);
086        }
087    }
088
089    /**
090     * XSLT extension function that converts the XML for a value between two different OpenCms content value types.
091     *
092     * @param value a node list that is expected to contain exactly one element, which should represent an XML content value
093     * @param sourceTypeName the original type of the value
094     * @param targetTypeName the type which the value should be converted to
095     * @param elementName the name that should be used
096     *
097     * @return a node list containing the converted XML value
098     */
099    public NodeList convertType(NodeList value, String sourceTypeName, String targetTypeName, String elementName) {
100
101        if (value.getLength() != 1) {
102            throw new RuntimeException("convertType must be passed exactly one node.");
103        }
104
105        // this is somewhat convoluted - XSLT uses the org.w3c.dom classes, while OpenCms XML contents use dom4j DOM classes.
106        // We create a dom4j document containing only the relevant XML for the value, then interpret it as an OpenCms value
107        // on which we can call getStringValue(). Then we create a new value in the dom4j document for the new type, and set its
108        // string value to the previously read string value. In the end, we have to translate everything back to org.w3c.dom format again.
109
110        Document doc = m_documentBuilder.newDocument();
111        Node copiedNode = doc.importNode(value.item(0), true);
112        Element w3cRoot = doc.createElement("root");
113        doc.appendChild(w3cRoot);
114        w3cRoot.appendChild(copiedNode);
115        org.dom4j.io.DOMReader reader = new DOMReader();
116        org.dom4j.Document dom4jDoc = reader.read(doc);
117        org.dom4j.Element dom4jRoot = dom4jDoc.getRootElement();
118        org.dom4j.Element dom4jValue = dom4jRoot.elements().get(0);
119        final CmsDefaultXmlContentHandler handler = new CmsDefaultXmlContentHandler();
120
121        // We need a dummy content with a content handler because creating a new value require a reference to a content,
122        // and the content handler is asked for a default value. This dummy content is probably unusable for anything else.
123        I_CmsXmlDocument dummyContent = new CmsXmlContent() {
124
125            public I_CmsXmlContentHandler getHandler() {
126
127                return handler;
128            }
129
130        };
131        I_CmsXmlContentValue sourceType = (I_CmsXmlContentValue)OpenCms.getXmlContentTypeManager().getContentType(
132            sourceTypeName);
133
134        // We must use newInstance here so the element name is set
135        I_CmsXmlContentValue targetType = (I_CmsXmlContentValue)OpenCms.getXmlContentTypeManager().getContentType(
136            targetTypeName).newInstance(elementName, "0", "1");
137
138        I_CmsXmlContentValue sourceValue = sourceType.createValue(dummyContent, dom4jValue, Locale.ENGLISH);
139        String valueString = sourceValue.getStringValue(m_cms);
140
141        // once we have the string value, we don't need the XML structure for the original value anymore - throw it away
142        dom4jValue.detach();
143
144        org.dom4j.Element dom4jNewValue = targetType.generateXml(m_cms, dummyContent, dom4jRoot, Locale.ENGLISH);
145        I_CmsXmlContentValue newValue = targetType.createValue(dummyContent, dom4jNewValue, Locale.ENGLISH);
146        newValue.setStringValue(m_cms, valueString);
147        List<Node> result = new ArrayList<>();
148        org.w3c.dom.Document newW3cDoc;
149        try {
150            newW3cDoc = new DOMWriter().write(dom4jDoc);
151            result.add(newW3cDoc.getDocumentElement().getFirstChild());
152        } catch (DocumentException e) {
153            LOG.error(e.getLocalizedMessage(), e);
154        }
155        return new NodeList() {
156
157            public int getLength() {
158
159                return result.size();
160            }
161
162            public Node item(int index) {
163
164                return result.get(index);
165            }
166
167        };
168    }
169}