PDFlib
KAUFEN
PDFlib

interactive/package_properties

Dump package properties. The properties are written to stdout record-by-record. In addition they are written to a CSV file that can be imported into spreadsheets.

Download Java Code     Show Output     Show CSV File Output     Show Input PDF

package com.pdflib.cookbook.pcos.interactive;


import java.io.File;

import java.io.FileOutputStream;

import java.io.PrintWriter;

import java.util.HashMap;

import java.util.Iterator;

import java.util.Map;

import java.util.Set;


import com.pdflib.pCOS;

import com.pdflib.pCOSException;

import com.pdflib.cookbook.pcos.pcos_cookbook_example;


/**

 * Dump package/portfolio properties. The properties are written to stdout

 * record-by-record. In addition they are written to a CSV file that can be

 * imported into spreadsheets.

 * <p>

 * Required software: pCOS interface 3 (pCOS 3.x, PDFlib+PDI/PPS 7.x, TET 2.2,

 * PLOP 3.x) <br>

 * Required data: PDF document that is a package or a portfolio and that has

 * package properties

 *

 * @version $Id: package_properties.java,v 1.12 2010/09/27 13:24:25 stm Exp $

 */

public class package_properties extends pcos_cookbook_example {


    /**

     * This is where the data files are. Adjust as necessary.

     */

    private final static String SEARCH_PATH = "../input";


    /**

     * The field separator character used in the CSV file output.

     */

    private final static char CSV_SEPARATOR_CHAR = ',';


    public void example_code(pCOS p, String filename) throws pCOSException,

        Exception {


        /* Open the PDF document */

        int doc = p.open_document(filename, "");

        if (doc == -1)

            throw new Exception("Error: " + p.get_errmsg());


        System.out.println("File name: " + p.pcos_get_string(doc, "filename"));

        System.out.println();


        /* Check whether the document represents a PDF package */

        String colltype = p.pcos_get_string(doc, "type:/Root/Collection");

        if (colltype.equals("dict")) {

            String defaultdoc;


            boolean has_folders = p.pcos_get_string(doc,

                "type:/Root/Collection/Folders").equals("dict");

            String doctype = has_folders ? "portfolio" : "package";


            System.out.print("PDF " + doctype + ", default document: ");


            /*

             * Check the default document (may be different from container PDF);

             * we currently don't check whether this name is actually present in

             * the list of embedded files.

             */

            if (p.pcos_get_string(doc, "type:/Root/Collection/D").equals(

                "string")) {

                defaultdoc = "'" + p.pcos_get_string(doc, "/Root/Collection/D")

                    + "'";

            }

            else {

                defaultdoc = "container PDF";

            }


            System.out.println(defaultdoc);


            File input_file = new File(filename);

            String basename = input_file.getName();

            String csv_name = basename + ".csv";


            System.out.println("Writing properties also to CSV file \""

                + csv_name + "\"");


            PrintWriter csv_file = new PrintWriter(new FileOutputStream(

                csv_name));

            print_package_properties(p, doc, has_folders, csv_file);

            csv_file.close();

        }

        else {

            System.out.println("Input document is not a PDF package");

        }


        p.close_document(doc);


        System.out.println();

    }


    /**

     * Description of a CollectionField object (see "TABLE 8.8 Entries in a

     * collection field dictionary" in the Adobe PDF Reference 1.7)

     */

    private class collection_field {

        /**

         * A text field.

         */

        public final static int TYPE_S = 0;

        /**

         * A date field.

         */

        public final static int TYPE_D = 1;

        /**

         * A number field.

         */

        public final static int TYPE_N = 2;

        /**

         * The field data is the file name of the embedded file stream.

         */

        public final static int TYPE_F = 3;

        /**

         * The field data is the description of the embedded file stream.

         */

        public final static int TYPE_DESC = 4;

        /**

         * The field data is the modification date of the embedded file stream.

         */

        public final static int TYPE_MODDATE = 5;

        /**

         * The field data is the creation date of the embedded file stream.

         */

        public final static int TYPE_CREATIONDATE = 6;

        /**

         * The field data is the creation date of the embedded file stream.

         */

        public final static int TYPE_SIZE = 7;


        /**

         * The "Subtype" element of the dictionary, one of the above "TYPE_..."

         * values.

         */

        public int subtype;


        /**

         * The textual field name that is displayed to the user.

         */

        public String display_name;


        /**

         * The relative order of the field name in the user interface.

         */

        public int order;


        /**

         * The initial visibility of the field in the user interface.

         */

        public boolean visibility;


        /**

         * Whether the field should be editable in the user interface.

         */

        public boolean editable;


        /**

         * Constructor with default values.

         */

        public collection_field() {

            subtype = -1;

            display_name = "";

            order = -1;

            visibility = true;

            editable = false;

        }

    }


    /**

     * An array to map the above integer-encoded field types to human-readable

     * strings.

     */

    public static final String type_names[] = { "text", "date", "number",

        "file name", "description", "modification date", "creation date",

        "size" };


    /**

     * Description of a schema dictionary. The class has fixed collection_field

     * members for the predefined file-related fields, and a Map that stores the

     * custom fields under their name as the key.

     *

     */

    private class schema_dictionary {

        /**

         * The number of predefined fields.

         */

        public static final int NUMBER_OF_PREDEFINED_FIELDS = 5;


        public collection_field predef_f;

        public collection_field predef_desc;

        public collection_field predef_moddate;

        public collection_field predef_creationdate;

        public collection_field predef_size;


        public Map custom_fields = new HashMap();


        /**

         * Default constructor that sets the predefined fields to defaults. If a

         * schema dictionary is present, it will override the built-in defaults.

         */

        public schema_dictionary() {

            predef_f = new collection_field();

            predef_f.display_name = "Name";

            predef_f.editable = true;

            predef_f.subtype = collection_field.TYPE_F;


            predef_desc = new collection_field();

            predef_desc.display_name = "Description";

            predef_desc.editable = true;

            predef_desc.subtype = collection_field.TYPE_DESC;


            predef_moddate = new collection_field();

            predef_moddate.display_name = "Modified Date";

            predef_moddate.subtype = collection_field.TYPE_MODDATE;


            predef_creationdate = new collection_field();

            predef_creationdate.display_name = "Creation Date";

            predef_creationdate.subtype = collection_field.TYPE_CREATIONDATE;


            predef_size = new collection_field();

            predef_size.display_name = "Size";

            predef_size.subtype = collection_field.TYPE_SIZE;

        }

    }


    /**

     * Contains the information of a parsed collection schema dictionary.

     */

    private schema_dictionary schema = new schema_dictionary();


    /**

     * Map of folder IDs to folder names (see "TABLE 8.6c Entries in a folder

     * dictionary" in "Adobe Supplement to the ISO 32000 BaseVersion: 1.7

     * ExtensionLevel: 3" document)

     * <p>

     * Every folder in a portfolio has a unique ID. For being able to display a

     * folder name for an item in a portfolio, the map is built once by

     * analyzing the folder structure under "/Root/Collection/Folders".

     */

    private HashMap folders = null;


    /**

     * Analyze the collection schema dictionary if present, and print out the

     * properties of all packages.

     *

     * @param p

     *            The {@link pCOS} object

     * @param doc

     *            A valid {@link pCOS} document handle

     * @param csv_file

     *            The {@link PrintWriter} object for producing the CSV file

     *

     * @throws pCOSException

     */

    private void print_package_properties(pCOS p, int doc, boolean has_folders,

        PrintWriter csv_file) throws pCOSException {

        analyze_schema(p, doc);

        if (has_folders) {

            analyze_folders(p, doc);

        }

        print_schema_properties();

        print_schema_properties(csv_file);

        print_properties(p, doc);

        print_properties(csv_file, p, doc);

    }


    /**

     * Parse the folder structure under the pCOS path "/Root/Collection/Folders"

     * and save the mapping of folder IDs to folder names in the

     * {@link #folders folders} member.

     *

     * @param p

     *            The {@link pCOS} object

     * @param doc

     *            A valid {@link pCOS} document handle

     *

     * @throws pCOSException

     */

    private void analyze_folders(pCOS p, int doc) throws pCOSException {

        folders = new HashMap();


        /*

         * In order to avoid very long pCOS pathnames, we operate with pCOS

         * object IDs here.

         */

        int root_folder_id = (int) p.pcos_get_number(doc,

            "pcosid:/Root/Collection/Folders");

        walk_folder_tree(p, doc, root_folder_id, "");

    }


    /**

     * Recursive method to walk the folder tree.

     *

     * @param p

     *            The {@link pCOS} object

     * @param doc

     *            A valid {@link pCOS} document handle

     * @param node_id

     *            The pCOS object ID of the current folder node

     * @param parent_folder_path

     *            The accumulated pathname for the parent folder

     *

     * @throws pCOSException

     */

    private void walk_folder_tree(pCOS p, int doc, int node_id,

        String parent_folder_path) throws pCOSException {


        boolean has_next = true;

        while (has_next) {

            /*

             * Actually every folder dictionary should have a /Name entry, but

             * we've seen documents where it is missing. For the root folder it

             * is usually empty.

             */

            String node_path = "objects[" + node_id + "]";

            String node_name_path = node_path + "/Name";

            String node_name = p.pcos_get_string(doc, "type:" + node_name_path)

                .equals("string") ? p.pcos_get_string(doc, node_name_path) : "";


            String node_id_path = node_path + "/ID";

            int folder_id = (int) p.pcos_get_number(doc, node_id_path);


            String separator = parent_folder_path.endsWith("/") ? "" : "/";

            String current_path = parent_folder_path + separator + node_name;

            folders.put(new Integer(folder_id), current_path);


            System.out.println("folder id " + folder_id + " path "

                + current_path);


            String child_path = node_path + "/Child";

            if (p.pcos_get_string(doc, "type:" + child_path).equals("dict")) {

                int child_node_id = (int) p.pcos_get_number(doc, "pcosid:"

                    + child_path);

                walk_folder_tree(p, doc, child_node_id, current_path);

            }


            String next_path = node_path + "/Next";

            has_next = p.pcos_get_string(doc, "type:" + next_path).equals(

                "dict");

            if (has_next) {

                node_id = (int) p.pcos_get_number(doc, "pcosid:" + next_path);

            }

        }

    }


    /**

     * Read in a collection field dictionary. Analyze, whether it describes one

     * of the predefined properties or a custom property, and save it in the

     * {@link #schema} member.

     *

     * @param p

     *            The pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param key

     *            The name of the entry in the collection schema dictionary

     * @param value_path

     *            The pCOS path for the collection field dictionary

     *

     * @throws pCOSException

     */

    private void read_collection_field(pCOS p, int doc, String key,

        String value_path) throws pCOSException {

        collection_field field = new collection_field();


        // The display name is mandatory.

        field.display_name = p.pcos_get_string(doc, value_path + "/N");


        // The relative order is optional.

        String order_path = value_path + "/O";

        String objtype = p.pcos_get_string(doc, "type:" + order_path);

        if (objtype.equals("integer")) {

            field.order = (int) p.pcos_get_number(doc, order_path);

        }


        // The visibility is optional.

        String visibility_path = value_path + "/V";

        objtype = p.pcos_get_string(doc, "type:" + visibility_path);

        if (objtype.equals("boolean")) {

            field.visibility = (int) p.pcos_get_number(doc, visibility_path) != 0;

        }


        // The editability is optional.

        String editable_path = value_path + "/E";

        objtype = p.pcos_get_string(doc, "type:" + editable_path);

        if (objtype.equals("boolean")) {

            field.editable = (int) p.pcos_get_number(doc, editable_path) != 0;

        }


        // The subtype is required.

        String subtype = p.pcos_get_string(doc, value_path + "/Subtype");

        if (subtype.equals("S")) {

            field.subtype = collection_field.TYPE_S;

            schema.custom_fields.put(key, field);

        }

        else if (subtype.equals("D")) {

            field.subtype = collection_field.TYPE_D;

            schema.custom_fields.put(key, field);

        }

        else if (subtype.equals("N")) {

            field.subtype = collection_field.TYPE_N;

            schema.custom_fields.put(key, field);

        }

        else if (subtype.equals("F")) {

            field.subtype = collection_field.TYPE_F;

            schema.predef_f = field;

        }

        else if (subtype.equals("Desc")) {

            field.subtype = collection_field.TYPE_DESC;

            schema.predef_desc = field;

        }

        else if (subtype.equals("ModDate")) {

            field.subtype = collection_field.TYPE_MODDATE;

            schema.predef_moddate = field;

        }

        else if (subtype.equals("CreationDate")) {

            field.subtype = collection_field.TYPE_CREATIONDATE;

            schema.predef_creationdate = field;

        }

        else if (subtype.equals("Size")) {

            field.subtype = collection_field.TYPE_SIZE;

            schema.predef_size = field;

        }

    }


    /**

     * Read in the collection schema dictionary.

     *

     * @param p

     *            The pCOS object

     * @param doc

     *            A valid pCOS document handle

     *

     * @throws pCOSException

     */

    private void analyze_schema(pCOS p, int doc) throws pCOSException {

        String schema_path = "/Root/Collection/Schema";

        String objtype = p.pcos_get_string(doc, "type:" + schema_path);


        if (objtype.equals("dict")) {

            int length = (int) p.pcos_get_number(doc, "length:" + schema_path);


            for (int i = 0; i < length; i += 1) {

                String schema_entry_path = schema_path + "[" + i + "]";

                String key = p.pcos_get_string(doc, schema_entry_path + ".key");

                String value_path = schema_entry_path + ".val";

                objtype = p.pcos_get_string(doc, "type:" + value_path);

                if (objtype.equals("dict")) {

                    read_collection_field(p, doc, key, value_path);

                }

                else if (key.equals("Type") && objtype.equals("name")) {

                    String dictionaryType = p.pcos_get_string(doc, value_path);

                    if (!dictionaryType.equals("CollectionSchema")) {

                        System.out.println("Illegal type \"" + dictionaryType

                            + "\" for collection schema dictionary");

                    }

                }

                else {

                    System.out

                        .println("Illegal entry in collection schema dictionary "

                            + "(key: " + key + ", objtype: " + objtype + ")");

                }

            }

        }

        else {

            System.out.println("No collection schema dictionary present");


            /*

             * Try to detect Acrobat 9 Portfolios. At the time this pCOS

             * Cookbook example was written, there was no documentation about

             * the Portfolio implementation available, so this is done

             * heuristically.

             */

            String acrobat9_entries[] = { "Folder", "Color", "Navigator",

                "Resources" };


            boolean acrobat9_assumed = false;

            for (int i = 0; i < acrobat9_entries.length && !acrobat9_assumed; i += 1) {

                String path = "/Root/Collection/" + acrobat9_entries[i];

                objtype = p.pcos_get_string(doc, "type:" + path);

                acrobat9_assumed = objtype.equals("dict");

            }


            if (acrobat9_assumed) {

                System.out.println("This looks like an Acrobat 9 Portfolio");

            }

        }

    }


    /**

     * Print the collection schema. This is a description of all the properties

     * that can be attached to the package members.

     *

     * @throws pCOSException

     */

    private void print_schema_properties() throws pCOSException {

        System.out.println();

        System.out.println("Collection schema:");


        int property_number = 1;

        print_schema_property(property_number++, schema.predef_f, null);

        print_schema_property(property_number++, schema.predef_desc, null);

        print_schema_property(property_number++, schema.predef_moddate, null);

        print_schema_property(property_number++, schema.predef_creationdate,

            null);

        print_schema_property(property_number++, schema.predef_size, null);


        Set customFields = schema.custom_fields.entrySet();

        Iterator i = customFields.iterator();

        while (i.hasNext()) {

            Map.Entry entry = (Map.Entry) i.next();

            String key = (String) entry.getKey();

            collection_field field_desc = (collection_field) entry.getValue();

            print_schema_property(property_number++, field_desc, key);

        }

    }


    /**

     * Print the header section of the CSV file that describes the collection

     * schema.

     *

     * @param csv_file

     *            The {@link PrintWriter} object for producing the CSV file

     */

    private void print_schema_properties(PrintWriter csv_file) {

        /*

         * Add 1 for the first columns with the row description.

         */

        int number_of_columns = schema_dictionary.NUMBER_OF_PREDEFINED_FIELDS

            + schema.custom_fields.size() + 1;


        /*

         * Add 1 for the folder name column if this is a portfolio.

         */

        if (folders != null) {

            number_of_columns += 1;

        }


        /*

         * Array with the row descriptions for the schema header section.

         */

        String row_descriptions[] = { "Field name", "Kind", "Type", "Order",

            "Visibility", "Editable" };


        /*

         * Allocate a two-dimensional String array to make it simpler to write

         * out the header section with the schema description.

         */

        String schema_header[][] = new String[row_descriptions.length][number_of_columns];


        /*

         * Transfer row_names.

         */

        int column_counter = 0;

        for (int i = 0; i < row_descriptions.length; i += 1) {

            schema_header[i][column_counter] = row_descriptions[i];

        }


        /*

         * Transfer column for folder name if this is a portfolio.

         */

        if (folders != null) {

            column_counter += 1;

            int row = 0;

            schema_header[row++][column_counter] = "Folder";

            schema_header[row++][column_counter] = "built-in";

            schema_header[row++][column_counter] = "folder name";

            schema_header[row++][column_counter] = "<n/a>";

            schema_header[row++][column_counter] = "<n/a>";

            schema_header[row++][column_counter] = "<n/a>";

        }


        /*

         * Transfer predefined fields.

         */

        column_counter += 1;

        csv_schema_property(schema_header, column_counter++, schema.predef_f,

            null);

        csv_schema_property(schema_header, column_counter++,

            schema.predef_desc, null);

        csv_schema_property(schema_header, column_counter++,

            schema.predef_moddate, null);

        csv_schema_property(schema_header, column_counter++,

            schema.predef_creationdate, null);

        csv_schema_property(schema_header, column_counter++,

            schema.predef_size, null);


        /*

         * Transfer custom fields.

         */

        Set customFields = schema.custom_fields.entrySet();

        Iterator i = customFields.iterator();

        while (i.hasNext()) {

            Map.Entry entry = (Map.Entry) i.next();

            String key = (String) entry.getKey();

            collection_field field_desc = (collection_field) entry.getValue();

            csv_schema_property(schema_header, column_counter++, field_desc,

                key);

        }


        for (int row = 0; row < row_descriptions.length; row += 1) {

            csv_row_description(csv_file, schema_header[row][0]);

            for (int column = 1; column < column_counter; column += 1) {

                csv_column(csv_file, schema_header[row][column]);

            }

            csv_row_complete(csv_file);

        }

    }


    /**

     * Print the description for a single property in the schema.

     *

     * @param i

     *            The relative number of the property

     * @param field

     *            The {@link collection_field} description of the field

     * @param key

     *            The key in the collection item (CI) dictionary for a custom

     *            property, null otherwise

     */

    private void print_schema_property(int i, collection_field field, String key) {

        print_formatted("Property number", "" + i);

        print_formatted("Property kind", key == null ? "built-in" : "custom");

        print_formatted("Display name", field.display_name);

        print_formatted("Type", type_names[field.subtype]);

        print_formatted("Order", field.order != -1 ? "" + field.order

            : "<not set>");

        print_formatted("Visibility", field.visibility ? "true" : "false");

        print_formatted("Editable", field.editable ? "true" : "false");

    }


    /**

     * Perform the printing of the actual properties. First the schema must have

     * been analyzed. As the lengths of the fields are unknown upfront, the

     * properties are printed record by record, to avoid getting ugly tables.

     * The Sort dictionary in the Collection dictionary is ignored, so the

     * properties are printed out in the order in which the documents appear in

     * the Names array.

     *

     * @param p

     *            The pCOS object

     * @param doc

     *            A valid pCOS document handle

     *

     * @throws pCOSException

     */

    private void print_properties(pCOS p, int doc) throws pCOSException {

        System.out.println();

        System.out.println("Properties of package members:");


        int filecount = (int) p.pcos_get_number(doc,

            "length:names/EmbeddedFiles");

        for (int file_index = 0; file_index < filecount; file_index++) {

            System.out.println("Package member #" + (file_index + 1) + ":");

            print_file_properties(p, doc, "names/EmbeddedFiles[" + file_index

                + "]");

        }

    }


    /**

     * Perform the printing of the actual properties to the CSV file.

     *

     * @param csv_file

     *            The CSV file

     * @param p

     *            The {@link pCOS} object

     * @param doc

     *            A valid {@link pCOS} document handle

     * @throws pCOSException

     */

    private void print_properties(PrintWriter csv_file, pCOS p, int doc)

        throws pCOSException {

        int filecount = (int) p.pcos_get_number(doc,

            "length:names/EmbeddedFiles");

        for (int file_index = 0; file_index < filecount; file_index++) {

            csv_row_description(csv_file, "Package member #" + (file_index + 1));

            print_file_properties(csv_file, p, doc, "names/EmbeddedFiles["

                + file_index + "]");

            csv_row_complete(csv_file);

        }

    }


    /**

     * Print the properties for a single file.

     *

     * @param p

     *            The pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_index

     *            The pCOS path for the EmbeddedFiles entry

     *

     * @throws pCOSException

     */

    private void print_file_properties(pCOS p, int doc, String file_path)

        throws pCOSException {

        if (folders != null) {

            print_folder_path(p, doc, file_path);

        }


        int property_number = 1;

        print_property(p, doc, property_number++, file_path, schema.predef_f,

            null);

        print_property(p, doc, property_number++, file_path,

            schema.predef_desc, null);

        print_property(p, doc, property_number++, file_path,

            schema.predef_moddate, null);

        print_property(p, doc, property_number++, file_path,

            schema.predef_creationdate, null);

        print_property(p, doc, property_number++, file_path,

            schema.predef_size, null);


        Set customFields = schema.custom_fields.entrySet();

        Iterator i = customFields.iterator();

        while (i.hasNext()) {

            Map.Entry entry = (Map.Entry) i.next();

            String key = (String) entry.getKey();

            collection_field field_desc = (collection_field) entry.getValue();

            print_property(p, doc, property_number++, file_path, field_desc,

                key);

        }

    }


    /**

     * Print out the folder pathname in the portfolio.

     *

     * @param p

     *            The pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path of the EmbeddedFiles entry

     *

     * @throws pCOSException

     */

    private void print_folder_path(pCOS p, int doc, String file_path)

        throws pCOSException {

        String folder = get_folder_path(p, doc, file_path);

        print_formatted("Folder", folder);

    }


    /**

     * Retrieve the folder path from an "EmbeddedFiles" entry.

     * <p>

     * Files in the "EmbeddedFiles" name tree are linked via a special naming

     * convention "&lt;id&gt;filename" to their folders. "id" is the folder id

     * that is stored in the folder dictionary.

     *

     * @param p

     *            The pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path of the EmbeddedFiles entry

     *

     * @return the folder name

     *

     * @throws pCOSException

     */

    private String get_folder_path(pCOS p, int doc, String file_path)

        throws pCOSException {

        String folder_link = p.pcos_get_string(doc, file_path + ".key");


        /*

         * The first character must be the '<' character if the file is

         * associated with a folder. The '>' must follow after at least one

         * character.

         */

        int lt_index = folder_link.indexOf('<');

        int gt_index = folder_link.indexOf('>');


        /*

         * Extract the folder id. If a name does not conform to the rules, the

         * file is treated as associated with the root folder.

         */

        String folder = "/";

        if (lt_index == 0 && gt_index >= 2) {

            String folder_id_string = folder_link.substring(1, gt_index);

            try {

                Integer folder_id = new Integer(folder_id_string);

                folder = (String) folders.get(folder_id);

            }

            catch (NumberFormatException e) {

                // Invalid id number in the folder id string, assume root folder

            }

        }


        return folder;

    }


    /**

     * Print out a single property to the CSV file.

     *

     * @param csv_file

     *            The CSV file

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param number

     *            The relative number of the property

     * @param file_path

     *            The pCOS path for the package member

     * @param field

     *            The @{link collection_field} description of the field

     * @param key

     *            The key in the "CI" dictionary for the package member

     *

     * @throws pCOSException

     */

    private void print_property(PrintWriter csv_file, pCOS p, int doc,

        String file_path, collection_field field, String key)

        throws pCOSException {

        switch (field.subtype) {

        case collection_field.TYPE_S:

            csv_column(csv_file, get_text_field(p, doc, file_path, key));

            break;

        case collection_field.TYPE_D:

            // we print dates as strings

            csv_column(csv_file, get_text_field(p, doc, file_path, key));

            break;

        case collection_field.TYPE_N:

            csv_column(csv_file, get_number_field(p, doc, file_path, key));

            break;

        case collection_field.TYPE_F:

            csv_column(csv_file, get_filename(p, doc, file_path));

            break;

        case collection_field.TYPE_DESC:

            csv_column(csv_file, get_description(p, doc, file_path));

            break;

        case collection_field.TYPE_MODDATE:

            csv_column(csv_file, get_moddate(p, doc, file_path));

            break;

        case collection_field.TYPE_CREATIONDATE:

            csv_column(csv_file, get_creationdate(p, doc, file_path));

            break;

        case collection_field.TYPE_SIZE:

            csv_column(csv_file, get_size(p, doc, file_path));

            break;

        }

    }


    /**

     * Print the properties for a single file to the CSV file.

     *

     * @param csv_file

     * @param p

     *            The pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_index

     *            The pCOS path for the EmbeddedFiles entry

     * @throws pCOSException

     */

    private void print_file_properties(PrintWriter csv_file, pCOS p, int doc,

        String file_path) throws pCOSException {

        if (folders != null) {

            csv_column(csv_file, get_folder_path(p, doc, file_path));

        }


        print_property(csv_file, p, doc, file_path, schema.predef_f, null);

        print_property(csv_file, p, doc, file_path, schema.predef_desc, null);

        print_property(csv_file, p, doc, file_path, schema.predef_moddate, null);

        print_property(csv_file, p, doc, file_path, schema.predef_creationdate,

            null);

        print_property(csv_file, p, doc, file_path, schema.predef_size, null);


        Set customFields = schema.custom_fields.entrySet();

        Iterator i = customFields.iterator();

        while (i.hasNext()) {

            Map.Entry entry = (Map.Entry) i.next();

            String key = (String) entry.getKey();

            collection_field field_desc = (collection_field) entry.getValue();

            print_property(csv_file, p, doc, file_path, field_desc, key);

        }

    }


    /**

     * Print out a single property.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param number

     *            The relative number of the property

     * @param file_path

     *            The pCOS path for the package member

     * @param field

     *            The @{link collection_field} description of the field

     * @param key

     *            The key in the "CI" dictionary for the package member

     *

     * @throws pCOSException

     */

    private void print_property(pCOS p, int doc, int number, String file_path,

        collection_field field, String key) throws pCOSException {

        print_formatted("Property number", "" + number);


        String value = null;

        switch (field.subtype) {

        case collection_field.TYPE_S:

            value = get_text_field(p, doc, file_path, key);

            break;

        case collection_field.TYPE_D:

            // we print dates as strings

            value = get_text_field(p, doc, file_path, key);

            break;

        case collection_field.TYPE_N:

            value = get_number_field(p, doc, file_path, key);

            break;

        case collection_field.TYPE_F:

            value = get_filename(p, doc, file_path);

            break;

        case collection_field.TYPE_DESC:

            value = get_description(p, doc, file_path);

            break;

        case collection_field.TYPE_MODDATE:

            value = get_moddate(p, doc, file_path);

            break;

        case collection_field.TYPE_CREATIONDATE:

            value = get_creationdate(p, doc, file_path);

            break;

        case collection_field.TYPE_SIZE:

            value = get_size(p, doc, file_path);

            break;

        }

        print_formatted("Display name", field.display_name);

        print_formatted("Value", value);

    }


    /**

     * Get the size for a package member.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path for the package member

     *

     * @return The size of the package member in bytes as String

     *

     * @throws pCOSException

     */

    private String get_size(pCOS p, int doc, String file_path)

        throws pCOSException {

        String size_path = file_path + "/EF/F/Params/Size";

        String retval = null;

        String objtype = p.pcos_get_string(doc, "type:" + size_path);

        if (objtype.equals("number")) {

            retval = "" + (int) p.pcos_get_number(doc, size_path);

        }

        return retval;

    }


    /**

     * Get the creation date of a package member.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path for the package member

     *

     * @return The creation date in PDF format as String

     *

     * @throws pCOSException

     */

    private String get_creationdate(pCOS p, int doc, String file_path)

        throws pCOSException {

        return get_date(p, doc, file_path + "/EF/F/Params/CreationDate");

    }


    /**

     * Get the modification date of a package member.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path for the package member

     *

     * @return The modification date in PDF format as String

     *

     * @throws pCOSException

     */

    private String get_moddate(pCOS p, int doc, String file_path)

        throws pCOSException {

        return get_date(p, doc, file_path + "/EF/F/Params/ModDate");

    }


    /**

     * Get the date from the given pCOS path.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param date_path

     *            The pCOS path for the desired date

     *

     * @return The date stored under the given pCOS path if it exists, otherwise

     *         null

     *

     * @throws pCOSException

     */

    private String get_date(pCOS p, int doc, String date_path)

        throws pCOSException {

        String retval = null;

        String objtype = p.pcos_get_string(doc, "type:" + date_path);

        if (objtype.equals("string")) {

            retval = p.pcos_get_string(doc, date_path);

        }

        return retval;

    }


    /**

     * Get the description of a package member.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path of the package member

     *

     * @return The description for the package member as String if it is

     *         available, othwise null

     *

     * @throws pCOSException

     */

    private String get_description(pCOS p, int doc, String file_path)

        throws pCOSException {

        String desc_path = file_path + "/Desc";


        String retval = null;

        String objtype = p.pcos_get_string(doc, "type:" + desc_path);

        if (objtype.equals("string")) {

            retval = p.pcos_get_string(doc, desc_path);

        }

        return retval;

    }


    /**

     * Get the filename of a package member. Only the "UF" and "F" entries are

     * examined.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path of the package member

     *

     * @return The filename of the package member if it exists, otherwise null

     *

     * @throws pCOSException

     */

    private String get_filename(pCOS p, int doc, String file_path)

        throws pCOSException {

        String fname_paths[] = { file_path + "/UF", file_path + "/F" };


        String retval = null;

        for (int i = 0; i < fname_paths.length && retval == null; i += 1) {

            String objtype = p.pcos_get_string(doc, "type:" + fname_paths[i]);


            if (objtype.equals("string")) {

                retval = p.pcos_get_string(doc, fname_paths[i]);

            }

        }


        return retval;

    }


    /**

     * Get a custom number field.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path of the package member

     * @param key

     *            The key of the custom field in the collection item dictionary

     *

     * @return The number as String if it is available, otherwise null

     *

     * @throws pCOSException

     */

    private String get_number_field(pCOS p, int doc, String file_path,

        String key) throws pCOSException {

        String result = null;


        String collection_item_path = file_path + "/CI/" + key;

        String objtype = p.pcos_get_string(doc, "type:" + collection_item_path);

        if (objtype.equals("number")) {

            result = "" + p.pcos_get_number(doc, collection_item_path);

        }

        else if (objtype.equals("dict")) {

            /*

             * A "collection subitem dictionary"

             */

            String data = "";

            String collection_subitem_data_path = collection_item_path + "/D";

            objtype = p.pcos_get_string(doc, "type:"

                + collection_subitem_data_path);

            if (objtype.equals("number")) {

                data = ""

                    + p.pcos_get_number(doc, collection_subitem_data_path);

            }


            String prefix = "";

            String collection_subitem_prefix_path = collection_item_path + "/P";

            objtype = p.pcos_get_string(doc, "type:"

                + collection_subitem_prefix_path);

            if (objtype.equals("string")) {

                prefix = p.pcos_get_string(doc, collection_subitem_prefix_path);

            }


            result = prefix + data;

        }


        return result;

    }


    /**

     * Get a custom text field. This is also used for date fields.

     *

     * @param p

     *            A pCOS object

     * @param doc

     *            A valid pCOS document handle

     * @param file_path

     *            The pCOS path of the package member

     * @param key

     *            The key of the custom field in the collection item dictionary

     *

     * @return The text/date as String if it is available, otherwise null

     *

     * @throws pCOSException

     */

    private String get_text_field(pCOS p, int doc, String file_path, String key)

        throws pCOSException {

        String result = null;


        String collection_item_path = file_path + "/CI/" + key;

        String objtype = p.pcos_get_string(doc, "type:" + collection_item_path);

        if (objtype.equals("string")) {

            result = p.pcos_get_string(doc, collection_item_path);

        }

        else if (objtype.equals("dict")) {

            /*

             * A "collection subitem dictionary"

             */

            String data = "";

            String collection_subitem_data_path = collection_item_path + "/D";

            objtype = p.pcos_get_string(doc, "type:"

                + collection_subitem_data_path);

            if (objtype.equals("string")) {

                data = p.pcos_get_string(doc, collection_subitem_data_path);

            }


            String prefix = "";

            String collection_subitem_prefix_path = collection_item_path + "/P";

            objtype = p.pcos_get_string(doc, "type:"

                + collection_subitem_prefix_path);

            if (objtype.equals("string")) {

                prefix = p.pcos_get_string(doc, collection_subitem_prefix_path);

            }


            result = prefix + data;

        }


        return result;

    }


    /**

     * Constant for aligning all the labels in the output.

     */

    private static final int DESC_WIDTH = 20;


    /**

     * Print label/value pairs, aligned using the {@link #DESC_WIDTH} constant.

     *

     * @param description

     *            The label

     * @param value

     *            The value

     */

    private void print_formatted(String description, String value) {

        StringBuffer b = new StringBuffer();

        for (int i = description.length(); i < DESC_WIDTH; i += 1) {

            b.append(' ');

        }

        b.append(description);

        b.append(": ");

        b.append(value != null ? value : "<null>");

        System.out.println(b.toString());

    }


    /**

     * Complete a row by writing a newline character.

     *

     * @param csv_file

     *            The CSV file.

     */

    private void csv_row_complete(PrintWriter csv_file) {

        csv_file.println();

    }


    /**

     * Print a field to a new CSV column.

     *

     * @param csv_file

     *            The CSV file.

     * @param value

     *            The value to put into the column.

     */

    private void csv_column(PrintWriter csv_file, String value) {

        csv_file.print(CSV_SEPARATOR_CHAR);

        csv_file.print(csv_escape(value));

    }


    /**

     * Print out the first column of a row, containing the description.

     *

     * @param csv_file

     *            The {@link PrintWriter} object for producing the CSV file

     * @param string

     *            The contents of the first column

     */

    private void csv_row_description(PrintWriter csv_file, String string) {

        String escaped_string = csv_escape(string);

        csv_file.print(escaped_string);

    }


    /**

     * Escape the string according to the rules for CSV files.

     *

     * @param string

     *            The string to escape

     *

     * @return The string quoted according to the CSV rules.

     */

    private String csv_escape(String string) {

        StringBuffer buffer = new StringBuffer();


        if (string != null) {

            boolean must_quote = false;

            for (int i = 0; i < string.length(); i += 1) {

                char c = string.charAt(i);


                switch (c) {

                case '"':

                    // escape the double quotes

                    buffer.append('"');

                case CSV_SEPARATOR_CHAR:

                case '\n':

                    must_quote = true;

                }

                buffer.append(c);

            }


            if (must_quote) {

                buffer.insert(0, '"');

                buffer.append('"');

            }

        }


        return buffer.toString();

    }


    /**

     * Store the description of a property in the given column of the

     * schema_header array.

     *

     * @param schema_header

     *            The target array

     * @param column

     *            The column to write to

     * @param field

     *            The field to store

     * @param key

     *            The key of a custom property

     */

    private void csv_schema_property(String[][] schema_header, int column,

        collection_field field, String key) {

        /*

         * Save the properties of the field, in the same order as they are

         * specified in the "row_descriptions" array in

         * "print_schema_properties".

         */

        int row = 0;

        schema_header[row++][column] = field.display_name;

        schema_header[row++][column] = key == null ? "built-in" : "custom";

        schema_header[row++][column] = type_names[field.subtype];

        schema_header[row++][column] = field.order != -1 ? "" + field.order

            : "<not set>";

        schema_header[row++][column] = field.visibility ? "true" : "false";

        schema_header[row++][column] = field.editable ? "true" : "false";

    }


    public package_properties(String[] argv, String readable_name,

        String search_path, String full_rcs_file_name, String revision) {

        super(argv, readable_name, search_path, full_rcs_file_name, revision);

    }


    public static void main(String argv[]) {

        package_properties example = new package_properties(argv,

            "Package properties", SEARCH_PATH,

            "$RCSfile: package_properties.java,v $", "$Revision: 1.12 $");

        example.execute();

    }

}