001/* 
002 * Copyright (C) 2016 Hobrasoft s.r.o.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU Affero General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU Affero General Public License for more details.
013 *
014 * You should have received a copy of the GNU Affero General Public License
015 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
016 */
017package cz.hobrasoft.pdfmu.operation.metadata;
018
019import com.itextpdf.text.pdf.PdfReader;
020import cz.hobrasoft.pdfmu.MapSorter;
021import cz.hobrasoft.pdfmu.PdfmuUtils;
022import cz.hobrasoft.pdfmu.PreferenceListComparator;
023import cz.hobrasoft.pdfmu.operation.args.ArgsConfiguration;
024import java.util.AbstractMap;
025import java.util.Arrays;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.SortedMap;
030import net.sourceforge.argparse4j.impl.Arguments;
031import net.sourceforge.argparse4j.inf.ArgumentGroup;
032import net.sourceforge.argparse4j.inf.ArgumentParser;
033import net.sourceforge.argparse4j.inf.Namespace;
034
035public class MetadataParameters implements ArgsConfiguration {
036
037    private Map<String, String> info = new HashMap<>();
038    private boolean clearall = false;
039
040    public Map<String, String> getInfo(PdfReader pdfReader) {
041        if (clearall) {
042            // We need the keys that are already set in the input file.
043            Map<String, String> inInfo = pdfReader.getInfo();
044            Map<String, String> res = new HashMap<>();
045            for (String key : inInfo.keySet()) {
046                // Unset the property `key`
047                // TODO?: Do not unset the properties "Producer" and "ModDate"
048                res.put(key, null);
049            }
050            // Set all the properties in `info`, possibly overwriting the unset properties
051            res.putAll(info);
052            return res;
053        }
054        return info;
055    }
056
057    // Table with the descriptions:
058    // http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf
059    // (table 317, section 14.3.3, page 550)
060    private static final Map<String, String> standardProperties = PdfmuUtils.sortedMap(
061            new AbstractMap.SimpleEntry<>("Title", "The document's title."),
062            new AbstractMap.SimpleEntry<>("Subject", "The subject of the document."),
063            new AbstractMap.SimpleEntry<>("Author", "The name of the person who created the document."),
064            new AbstractMap.SimpleEntry<>("Keywords", "Keywords associated with the document."),
065            new AbstractMap.SimpleEntry<>("Creator", "The name of the product that created the document in the original format."),
066            new AbstractMap.SimpleEntry<>("Producer", "The name of the product that converted the document from the original format to PDF."),
067            new AbstractMap.SimpleEntry<>("CreationDate", "The date and time the document was created, in human-readable form."),
068            new AbstractMap.SimpleEntry<>("ModDate", "The date and time the document was most recently modified, in human-readable form."),
069            new AbstractMap.SimpleEntry<>("Trapped", "Has the document been modified to include trapping information? (recommended values: True,False,Unknown)")
070    );
071
072    // iText does not let us set the Producer property.
073    // The ModDate property also seems to be set automatically.
074    // TODO: Check spelling of "settable"
075    private static final List<String> standardSettableProperties = Arrays.asList(new String[]{
076        "Title", "Subject", "Author", "Keywords", "Creator", "CreationDate", "Trapped"});
077
078    @Override
079    public void addArguments(ArgumentParser parser) {
080        // Generic properties
081        parser.addArgument("-s", "--set")
082                .help("set the property P to the value V")
083                .metavar("P", "V")
084                .nargs(2)
085                .type(String.class)
086                .action(Arguments.append());
087
088        parser.addArgument("-c", "--clear")
089                .help("clear the property P")
090                .metavar("P")
091                .type(String.class)
092                .action(Arguments.append());
093
094        // Remove all properties
095        parser.addArgument("--clear-all")
096                .help("clear all the properties")
097                .type(boolean.class)
098                .action(Arguments.storeTrue());
099
100        // Standard properties
101        ArgumentGroup group = parser.addArgumentGroup("standard properties")
102                .description("These are simple shortcuts for setting the standard properties. For example, `--Title TITLE` is equivalent to `--set Title TITLE`.");
103        for (String property : standardSettableProperties) {
104            assert standardProperties.containsKey(property);
105            String help = standardProperties.get(property);
106            group.addArgument("--" + property)
107                    .help(help)
108                    .type(String.class);
109        }
110    }
111
112    @Override
113    public void setFromNamespace(Namespace namespace) {
114        clearall = namespace.getBoolean("clear_all");
115
116        // Clear the selected properties
117        { // clearedProperties
118            List<String> clearedProperties = namespace.getList("clear");
119            if (clearedProperties != null) {
120                for (String p : clearedProperties) {
121                    info.put(p, null);
122                }
123            }
124        }
125
126        // Generic properties
127        List<List<String>> elements = namespace.getList("set");
128        if (elements != null) {
129            for (List<String> element : elements) {
130                String key = element.get(0);
131                // TODO: Warn if key is Producer or ModDate
132                String value = element.get(1);
133                // TODO: Warn if key is Trapped and value is not one of True, False, Unknown
134                info.put(key, value);
135            }
136        }
137
138        // Standard properties
139        for (String property : standardSettableProperties) {
140            String value = namespace.getString(property);
141            if (value != null) {
142                info.put(property, value);
143            }
144        }
145    }
146
147    public void setFromInfo(Map<String, String> info) {
148        this.info = info;
149    }
150
151    private static final MapSorter<String> propertySorter
152            = new PreferenceListComparator<>(standardProperties.keySet().iterator());
153
154    public SortedMap<String, String> getSorted() {
155        return propertySorter.sort(info);
156    }
157
158}