001/*
002 * $Id: XmpReader.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.xml.xmp;
045
046import java.io.ByteArrayInputStream;
047import java.io.ByteArrayOutputStream;
048import java.io.IOException;
049
050import javax.xml.parsers.DocumentBuilder;
051import javax.xml.parsers.DocumentBuilderFactory;
052import javax.xml.parsers.ParserConfigurationException;
053
054import org.w3c.dom.Document;
055import org.w3c.dom.NamedNodeMap;
056import org.w3c.dom.Node;
057import org.w3c.dom.NodeList;
058import org.xml.sax.SAXException;
059
060import com.itextpdf.text.ExceptionConverter;
061import com.itextpdf.text.xml.XmlDomWriter;
062
063/**
064 * Reads an XMP stream into an org.w3c.dom.Document objects.
065 * Allows you to replace the contents of a specific tag.
066 * @since 2.1.3
067 */
068
069public class XmpReader {
070
071    private Document domDocument;
072    
073    /**
074     * Constructs an XMP reader
075     * @param   bytes   the XMP content
076     * @throws ExceptionConverter 
077     * @throws IOException 
078     * @throws SAXException 
079     */
080        public XmpReader(byte[] bytes) throws SAXException, IOException {
081                try {
082                DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
083                fact.setNamespaceAware(true);
084                        DocumentBuilder db = fact.newDocumentBuilder();
085                ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
086                domDocument = db.parse(bais);
087                } catch (ParserConfigurationException e) {
088                        throw new ExceptionConverter(e);
089                }
090        }
091        
092        /**
093         * Replaces the content of a tag.
094         * @param       namespaceURI    the URI of the namespace
095         * @param       localName               the tag name
096         * @param       value                   the new content for the tag
097         * @return      true if the content was successfully replaced
098         * @since       2.1.6 the return type has changed from void to boolean
099         */
100        public boolean replaceNode(String namespaceURI, String localName, String value) {
101                NodeList nodes = domDocument.getElementsByTagNameNS(namespaceURI, localName);
102                Node node;
103                if (nodes.getLength() == 0)
104                        return false;
105                for (int i = 0; i < nodes.getLength(); i++) {
106                        node = nodes.item(i);
107                        setNodeText(domDocument, node, value);
108                }
109                return true;
110        }
111
112        /**
113         * Replaces the content of an attribute in the description tag.
114         * @param       namespaceURI    the URI of the namespace
115         * @param       localName               the tag name
116         * @param       value                   the new content for the tag
117         * @return      true if the content was successfully replaced
118         * @since       5.0.0 the return type has changed from void to boolean
119         */
120        public boolean replaceDescriptionAttribute(String namespaceURI, String localName, String value) {
121                NodeList descNodes = domDocument.getElementsByTagNameNS("http://www.w3.org/1999/02/22-rdf-syntax-ns#","Description");
122                if(descNodes.getLength() == 0) {
123                        return false;
124                }
125                Node node;
126                for(int i = 0; i < descNodes.getLength(); i++) {
127                        node = descNodes.item(i);
128                        Node attr = node.getAttributes().getNamedItemNS(namespaceURI, localName);
129                        if(attr != null) {
130                                attr.setNodeValue(value);
131                                return true;
132                        }
133                }
134                return false;
135        }
136        
137        /**
138         * Adds a tag.
139         * @param       namespaceURI    the URI of the namespace
140         * @param       parent                  the tag name of the parent
141         * @param       localName               the name of the tag to add
142         * @param       value                   the new content for the tag
143         * @return      true if the content was successfully added
144         * @since       2.1.6
145         */
146        public boolean add(String parent, String namespaceURI, String localName, String value) {
147                NodeList nodes = domDocument.getElementsByTagName(parent);
148                if (nodes.getLength() == 0)
149                        return false;
150                Node pNode;
151                Node node;
152                for (int i = 0; i < nodes.getLength(); i++) {
153                        pNode = nodes.item(i);
154                        NamedNodeMap attrs = pNode.getAttributes();
155                        for (int j = 0; j < attrs.getLength(); j++) {
156                                node = attrs.item(j);
157                                if (namespaceURI.equals(node.getNodeValue())) {
158                                        node = domDocument.createElement(localName);
159                                        node.appendChild(domDocument.createTextNode(value));
160                                        pNode.appendChild(node);
161                                        return true;
162                                }
163                        }
164                }
165                return false;
166        }
167        
168    /**
169     * Sets the text of this node. All the child's node are deleted and a new
170     * child text node is created.
171     * @param domDocument the <CODE>Document</CODE> that contains the node
172     * @param n the <CODE>Node</CODE> to add the text to
173     * @param value the text to add
174     */
175    public boolean setNodeText(Document domDocument, Node n, String value) {
176        if (n == null)
177            return false;
178        Node nc = null;
179        while ((nc = n.getFirstChild()) != null) {
180            n.removeChild(nc);
181        }
182        n.appendChild(domDocument.createTextNode(value));
183        return true;
184    }
185        
186    /**
187     * Writes the document to a byte array.
188     */
189        public byte[] serializeDoc() throws IOException {
190                XmlDomWriter xw = new XmlDomWriter();
191        ByteArrayOutputStream fout = new ByteArrayOutputStream();
192        xw.setOutput(fout, null);
193        fout.write(XmpWriter.XPACKET_PI_BEGIN.getBytes("UTF-8"));
194        fout.flush();
195        NodeList xmpmeta = domDocument.getElementsByTagName("x:xmpmeta");
196        xw.write(xmpmeta.item(0));
197        fout.flush();
198                for (int i = 0; i < 20; i++) {
199                        fout.write(XmpWriter.EXTRASPACE.getBytes());
200                }
201        fout.write(XmpWriter.XPACKET_PI_END_W.getBytes());
202        fout.close();
203        return fout.toByteArray();
204        }
205}