From 667b607ca0bd2cc882bfdfd88f1705339eb1c17d Mon Sep 17 00:00:00 2001
From: Allen Byrne <50328838+byrnHDF@users.noreply.github.com>
Date: Mon, 29 Apr 2024 07:37:15 -0500
Subject: [PATCH] Add navigate chapters and use release_docs in Learn Basics
(#4441)
---
configure.ac | 2 +-
doxygen/CMakeLists.txt | 2 +-
doxygen/dox/LearnBasics1.dox | 19 +
doxygen/dox/LearnBasics2.dox | 10 +
doxygen/dox/LearnBasics3.dox | 31 +-
doxygen/dox/TrainingVideos.dox | 2 +
doxygen/examples/h5_attribute.c | 293 ++++++++++++++
doxygen/examples/h5_extlink.c | 662 ++++++++++++++++++++++++++++++++
8 files changed, 1013 insertions(+), 8 deletions(-)
create mode 100644 doxygen/examples/h5_attribute.c
create mode 100644 doxygen/examples/h5_extlink.c
diff --git a/configure.ac b/configure.ac
index 394c05959ca..04bb2f25116 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1543,7 +1543,7 @@ if test "X$HDF5_DOXYGEN" = "Xyes"; then
DOXYGEN_OPTIMIZE_OUTPUT_FOR_C=YES
DOXYGEN_MACRO_EXPANSION=YES
DOXYGEN_OUTPUT_DIRECTORY=hdf5lib_docs
- DOXYGEN_EXAMPLES_DIRECTORY='$(SRCDIR)/doxygen/dox/cookbook $(SRCDIR)/doxygen/examples $(SRCDIR)/src $(SRCDIR)/examples $(SRCDIR)/test'
+ DOXYGEN_EXAMPLES_DIRECTORY='$(SRCDIR)/doxygen/dox/cookbook $(SRCDIR)/doxygen/examples $(SRCDIR)/src $(SRCDIR)/release_docs $(SRCDIR)/test'
DOXYGEN_LAYOUT_FILE='$(SRCDIR)/doxygen/hdf5doxy_layout.xml'
DOXYGEN_HTML_HEADER='$(SRCDIR)/doxygen/hdf5_header.html'
DOXYGEN_HTML_FOOTER='$(SRCDIR)/doxygen/hdf5_footer.html'
diff --git a/doxygen/CMakeLists.txt b/doxygen/CMakeLists.txt
index 7dd7660621d..a3ee438cb8a 100644
--- a/doxygen/CMakeLists.txt
+++ b/doxygen/CMakeLists.txt
@@ -17,7 +17,7 @@ if (DOXYGEN_FOUND)
set (DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES)
set (DOXYGEN_MACRO_EXPANSION YES)
set (DOXYGEN_OUTPUT_DIRECTORY ${HDF5_BINARY_DIR}/hdf5lib_docs)
- set (DOXYGEN_EXAMPLES_DIRECTORY "${HDF5_DOXYGEN_DIR}/dox/cookbook ${HDF5_DOXYGEN_DIR}/examples ${HDF5_SRC_DIR} ${HDF5_SOURCE_DIR}/examples ${HDF5_TEST_SRC_DIR}")
+ set (DOXYGEN_EXAMPLES_DIRECTORY "${HDF5_DOXYGEN_DIR}/dox/cookbook ${HDF5_DOXYGEN_DIR}/examples ${HDF5_SRC_DIR} ${HDF5_SOURCE_DIR}/release_docs ${HDF5_TEST_SRC_DIR}")
set (DOXYGEN_LAYOUT_FILE ${HDF5_DOXYGEN_DIR}/hdf5doxy_layout.xml)
set (DOXYGEN_HTML_HEADER ${HDF5_DOXYGEN_DIR}/hdf5_header.html)
set (DOXYGEN_HTML_FOOTER ${HDF5_DOXYGEN_DIR}/hdf5_footer.html)
diff --git a/doxygen/dox/LearnBasics1.dox b/doxygen/dox/LearnBasics1.dox
index d4aa35d8fbb..64ba30ea9bf 100644
--- a/doxygen/dox/LearnBasics1.dox
+++ b/doxygen/dox/LearnBasics1.dox
@@ -21,6 +21,9 @@ directories and files, an HDF5 object in an HDF5 file is often referred to by it
\li /foo/zoo
signifies a member of the group foo, which in turn is a member of the root group.
+
"import hdf.hdf5lib.H5;
+
+Previous Chapter \ref LBAPI - Next Chapter \ref LBFileCreate
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBFileCreate Creating an HDF5 File
@@ -395,6 +404,9 @@ The simplified DDL for file definition is as follows:
::= |
\endcode
+
+Previous Chapter \ref LBProg - Next Chapter \ref LBDsetCreate
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBDsetCreate Creating a Dataset
@@ -712,6 +724,9 @@ The following is the simplified DDL dataset definition:
\endcode
+
+Previous Chapter \ref LBFileCreate - Next Chapter \ref LBDsetRW
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBDsetRW Reading From and Writing To a Dataset
@@ -852,6 +867,8 @@ Shown below is the contents of dsetf.h5 (created by the FORTRAN program).
\endcode
+Previous Chapter \ref LBDsetCreate - Next Chapter \ref LBAttrCreate
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBAttrCreate Creating an Attribute
@@ -1018,6 +1035,8 @@ ATTRIBUTE "attr" {
\endcode
+Previous Chapter \ref LBDsetRW - Next Chapter \ref LBGrpCreate
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
*/
diff --git a/doxygen/dox/LearnBasics2.dox b/doxygen/dox/LearnBasics2.dox
index 8eda57bc0c2..0df7d9ab620 100644
--- a/doxygen/dox/LearnBasics2.dox
+++ b/doxygen/dox/LearnBasics2.dox
@@ -63,6 +63,8 @@ GROUP "/" {
\endcode
+Previous Chapter \ref LBAttrCreate - Next Chapter \ref LBGrpCreateNames
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBGrpCreateNames Creating Groups using Absolute and Relative Names
@@ -189,6 +191,8 @@ GROUP "/" {
\endcode
+Previous Chapter \ref LBGrpCreate - Next Chapter \ref LBGrpDset
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBGrpDset Creating Datasets in Groups
@@ -298,6 +302,8 @@ DATASET "dset1" {
\endcode
+Previous Chapter \ref LBGrpCreateNames - Next Chapter \ref LBDsetSubRW
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBDsetSubRW Reading From or Writing To a Subset of a Dataset
@@ -477,6 +483,8 @@ example code. The memory dataspace was defined as one-dimensional.
for these parameters, rather than passing in an array for each, and for Fortran 90 you can omit these parameters.
+Previous Chapter \ref LBGrpDset - Next Chapter \ref LBDatatypes
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBDatatypes Datatype Basics
@@ -1159,6 +1167,8 @@ If nested VL datatypes were used to create the buffer, this routine frees them f
releasing all the memory without creating memory leaks.
+Previous Chapter \ref LBDsetSubRW - Next Chapter \ref LBPropsList
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
*/
diff --git a/doxygen/dox/LearnBasics3.dox b/doxygen/dox/LearnBasics3.dox
index ce907d4f6a5..ca9ba8bdc4a 100644
--- a/doxygen/dox/LearnBasics3.dox
+++ b/doxygen/dox/LearnBasics3.dox
@@ -39,6 +39,8 @@ list of the property types.
\li Close the property list when done, using #H5Pclose.
+Previous Chapter \ref LBDatatypes - Next Chapter \ref LBDsetLayout
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBDsetLayout Dataset Storage Layout
@@ -171,6 +173,8 @@ to a new with a new layout.
\see \ref sec_plist in the HDF5 \ref UG.
+Previous Chapter \ref LBPropsList - Next Chapter \ref LBExtDset
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@@ -216,6 +220,8 @@ after this call, the dataset's dataspace must be refreshed with #H5Dget_space be
\li Once there is no longer a need for a Property List instance, it should be closed with the #H5Pclose call.
+Previous Chapter \ref LBDsetLayout - Next Chapter \ref LBComDset
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBComDset Compressed Datasets
@@ -258,6 +264,8 @@ to #H5Dcreate will fail if attempting to create an SZIP compressed dataset with
The conflict can only be detected when the property list is used.
+Previous Chapter \ref LBExtDset - Next Chapter \ref LBContents
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBContents Discovering the Contents of an HDF5 File
@@ -302,6 +310,8 @@ The h5ex_g_visit example traverses a file using H5Ovisit and H5Lvisit:
\li F90: h5ex_g_visit_F03.f90
+Previous Chapter \ref LBComDset - Next Chapter \ref LBQuiz
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBQuiz Learning the basics QUIZ
@@ -398,6 +408,8 @@ is in the root
group. How woul
+Previous Chapter \ref LBContents - Next Chapter \ref LBQuizAnswers
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
@page LBQuizAnswers Learning the basics QUIZ with Answers
@@ -691,6 +703,8 @@ did = H5Dopen (file_id, "/foo/boo/moo"); /* absolute path */
+Previous Chapter \ref LBQuiz - Next Chapter \ref LBCompiling
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
/** @page LBCompiling Compiling HDF5 Applications
@@ -747,6 +761,9 @@ The h5cc, h5c++, and h5fc compile scripts come with the HDF5 binary distribution
libraries, and utilities) for the platforms we support. The h5c++ and h5fc utilities are ONLY present
if the library was built with C++ and Fortran.
+USING_HDF5_CMake.txt:
+\verbinclude USING_HDF5_CMake.txt
+
\section secLBCompilingVS Using Visual Studio
1. If you are building on 64-bit Windows, find the "Platform" dropdown
@@ -811,7 +828,7 @@ HDF5 C Library
\code
libhdf5_hl_cpp.a
libhdf5_cpp.a
-libhdf5hl_fortran.a
+libhdf5_hl_fortran.a
libhdf5_fortran.a
libhdf5_hl.a
libhdf5.a
@@ -821,7 +838,7 @@ libhdf5.a
\code
libhdf5_hl_cpp.a
libhdf5_cpp.a
-libhdf5hl_fortran.a
+libhdf5_hl_fortran.a
libhdf5_fortran.a
libhdf5_hl.a
libhdf5.a
@@ -832,7 +849,7 @@ libhdf5.a
\code
libhdf5_hl_cpp.lib
libhdf5_cpp.lib
-libhdf5hl_fortran.lib
+libhdf5_hl_fortran.lib
libhdf5_fortran.lib
libhdf5_hl.lib
libhdf5.lib
@@ -863,7 +880,7 @@ HDF5 C Library
\code
libhdf5_hl_cpp.so
libhdf5_cpp.so
-libhdf5hl_fortran.so
+libhdf5_hl_fortran.so
libhdf5_fortran.so
libhdf5_hl.so
libhdf5.so
@@ -873,7 +890,7 @@ libhdf5.so
\code
libhdf5_hl_cpp.dylib
libhdf5_cpp.dylib
-libhdf5hl_fortran.dylib
+libhdf5_hl_fortran.dylib
libhdf5_fortran.dylib
libhdf5_hl.dylib
libhdf5.dylib
@@ -883,7 +900,7 @@ libhdf5.dylib
\code
hdf5_hl_cpp.lib
hdf5_cpp.lib
-hdf5hl_fortran.lib
+hdf5_hl_fortran.lib
hdf5_fortran.lib
hdf5_hl.lib
hdf5.lib
@@ -1001,6 +1018,8 @@ For example, on Unix the log files will be in:
There are log files for the configure, test, and build.
+Previous Chapter \ref LBQuizAnswers - Next Chapter \ref LBTraining
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
*/
diff --git a/doxygen/dox/TrainingVideos.dox b/doxygen/dox/TrainingVideos.dox
index be5f557b683..8e15176f9f4 100644
--- a/doxygen/dox/TrainingVideos.dox
+++ b/doxygen/dox/TrainingVideos.dox
@@ -40,6 +40,8 @@ Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
+Previous Chapter \ref LBCompiling
+
Navigate back: \ref index "Main" / \ref GettingStarted / \ref LearnBasics
*/
diff --git a/doxygen/examples/h5_attribute.c b/doxygen/examples/h5_attribute.c
new file mode 100644
index 00000000000..6d3523d5b9e
--- /dev/null
+++ b/doxygen/examples/h5_attribute.c
@@ -0,0 +1,293 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Copyright by The HDF Group. *
+ * All rights reserved. *
+ * *
+ * This file is part of HDF5. The full HDF5 copyright notice, including *
+ * terms governing use, modification, and redistribution, is contained in *
+ * the COPYING file, which can be found at the root of the source code *
+ * distribution tree, or in https://www.hdfgroup.org/licenses. *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*
+ * This program illustrates the usage of the H5A Interface functions.
+ * It creates and writes a dataset, and then creates and writes array,
+ * scalar, and string attributes of the dataset.
+ * Program reopens the file, attaches to the scalar attribute using
+ * attribute name and reads and displays its value. Then index of the
+ * third attribute is used to read and display attribute values.
+ * The H5Aiterate function is used to iterate through the dataset attributes,
+ * and display their names. The function is also reads and displays the values
+ * of the array attribute.
+ */
+
+#include
+
+#include "hdf5.h"
+
+#define H5FILE_NAME "Attributes.h5"
+
+#define RANK 1 /* Rank and size of the dataset */
+#define SIZE 7
+
+#define ARANK 2 /* Rank and dimension sizes of the first dataset attribute */
+#define ADIM1 2
+#define ADIM2 3
+#define ANAME "Float attribute" /* Name of the array attribute */
+#define ANAMES "Character attribute" /* Name of the string attribute */
+
+static herr_t attr_info(hid_t loc_id, const char *name, const H5A_info_t *ainfo, void *opdata);
+/* Operator function */
+
+int
+main(void)
+{
+
+ hid_t file, dataset; /* File and dataset identifiers */
+
+ hid_t fid; /* Dataspace identifier */
+ hid_t attr1, attr2, attr3; /* Attribute identifiers */
+ hid_t attr;
+ hid_t aid1, aid2, aid3; /* Attribute dataspace identifiers */
+ hid_t atype, atype_mem; /* Attribute type */
+ H5T_class_t type_class;
+
+ hsize_t fdim[] = {SIZE};
+ hsize_t adim[] = {ADIM1, ADIM2}; /* Dimensions of the first attribute */
+
+ float matrix[ADIM1][ADIM2]; /* Attribute data */
+
+ herr_t ret; /* Return value */
+ H5O_info2_t oinfo; /* Object info */
+ unsigned i, j; /* Counters */
+ char string_out[80]; /* Buffer to read string attribute back */
+ int point_out; /* Buffer to read scalar attribute back */
+
+ /*
+ * Data initialization.
+ */
+ int vector[] = {1, 2, 3, 4, 5, 6, 7}; /* Dataset data */
+ int point = 1; /* Value of the scalar attribute */
+ char string[] = "ABCD"; /* Value of the string attribute */
+
+ for (i = 0; i < ADIM1; i++) { /* Values of the array attribute */
+ for (j = 0; j < ADIM2; j++)
+ matrix[i][j] = -1.;
+ }
+
+ /*
+ * Create a file.
+ */
+ file = H5Fcreate(H5FILE_NAME, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+
+ /*
+ * Create the dataspace for the dataset in the file.
+ */
+ fid = H5Screate(H5S_SIMPLE);
+ ret = H5Sset_extent_simple(fid, RANK, fdim, NULL);
+
+ /*
+ * Create the dataset in the file.
+ */
+ dataset = H5Dcreate2(file, "Dataset", H5T_NATIVE_INT, fid, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+
+ /*
+ * Write data to the dataset.
+ */
+ ret = H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, vector);
+
+ /*
+ * Create dataspace for the first attribute.
+ */
+ aid1 = H5Screate(H5S_SIMPLE);
+ ret = H5Sset_extent_simple(aid1, ARANK, adim, NULL);
+
+ /*
+ * Create array attribute.
+ */
+ attr1 = H5Acreate2(dataset, ANAME, H5T_NATIVE_FLOAT, aid1, H5P_DEFAULT, H5P_DEFAULT);
+
+ /*
+ * Write array attribute.
+ */
+ ret = H5Awrite(attr1, H5T_NATIVE_FLOAT, matrix);
+
+ /*
+ * Create scalar attribute.
+ */
+ aid2 = H5Screate(H5S_SCALAR);
+ attr2 = H5Acreate2(dataset, "Integer attribute", H5T_NATIVE_INT, aid2, H5P_DEFAULT, H5P_DEFAULT);
+
+ /*
+ * Write scalar attribute.
+ */
+ ret = H5Awrite(attr2, H5T_NATIVE_INT, &point);
+
+ /*
+ * Create string attribute.
+ */
+ aid3 = H5Screate(H5S_SCALAR);
+ atype = H5Tcopy(H5T_C_S1);
+ H5Tset_size(atype, 5);
+ H5Tset_strpad(atype, H5T_STR_NULLTERM);
+ attr3 = H5Acreate2(dataset, ANAMES, atype, aid3, H5P_DEFAULT, H5P_DEFAULT);
+
+ /*
+ * Write string attribute.
+ */
+ ret = H5Awrite(attr3, atype, string);
+
+ /*
+ * Close attribute and file dataspaces, and datatype.
+ */
+ ret = H5Sclose(aid1);
+ ret = H5Sclose(aid2);
+ ret = H5Sclose(aid3);
+ ret = H5Sclose(fid);
+ ret = H5Tclose(atype);
+
+ /*
+ * Close the attributes.
+ */
+ ret = H5Aclose(attr1);
+ ret = H5Aclose(attr2);
+ ret = H5Aclose(attr3);
+
+ /*
+ * Close the dataset.
+ */
+ ret = H5Dclose(dataset);
+
+ /*
+ * Close the file.
+ */
+ ret = H5Fclose(file);
+
+ /*
+ * Reopen the file.
+ */
+ file = H5Fopen(H5FILE_NAME, H5F_ACC_RDONLY, H5P_DEFAULT);
+
+ /*
+ * Open the dataset.
+ */
+ dataset = H5Dopen2(file, "Dataset", H5P_DEFAULT);
+
+ /*
+ * Attach to the scalar attribute using attribute name, then read and
+ * display its value.
+ */
+ attr = H5Aopen(dataset, "Integer attribute", H5P_DEFAULT);
+ ret = H5Aread(attr, H5T_NATIVE_INT, &point_out);
+ printf("The value of the attribute \"Integer attribute\" is %d \n", point_out);
+ ret = H5Aclose(attr);
+
+ //! [H5Oget_info3_snip]
+
+ /*
+ * Find string attribute by iterating through all attributes
+ */
+ ret = H5Oget_info3(dataset, &oinfo, H5O_INFO_NUM_ATTRS);
+ for (i = 0; i < (unsigned)oinfo.num_attrs; i++) {
+ attr = H5Aopen_by_idx(dataset, ".", H5_INDEX_CRT_ORDER, H5_ITER_INC, (hsize_t)i, H5P_DEFAULT,
+ H5P_DEFAULT);
+ atype = H5Aget_type(attr);
+ type_class = H5Tget_class(atype);
+ if (type_class == H5T_STRING) {
+ atype_mem = H5Tget_native_type(atype, H5T_DIR_ASCEND);
+ ret = H5Aread(attr, atype_mem, string_out);
+ printf("Found string attribute; its index is %d , value = %s \n", i, string_out);
+ ret = H5Tclose(atype_mem);
+ }
+ ret = H5Aclose(attr);
+ ret = H5Tclose(atype);
+ }
+
+ //! [H5Oget_info3_snip]
+ /*
+ * Get attribute info using iteration function.
+ */
+ ret = H5Aiterate2(dataset, H5_INDEX_NAME, H5_ITER_INC, NULL, attr_info, NULL);
+
+ /*
+ * Close the dataset and the file.
+ */
+ H5Dclose(dataset);
+ H5Fclose(file);
+
+ return 0;
+}
+
+/*
+ * Operator function.
+ */
+static herr_t
+attr_info(hid_t loc_id, const char *name, const H5A_info_t *ainfo, void *opdata)
+{
+ hid_t attr, atype, aspace; /* Attribute, datatype and dataspace identifiers */
+ int rank;
+ hsize_t sdim[64];
+ herr_t ret;
+ int i;
+ size_t npoints; /* Number of elements in the array attribute. */
+ float *float_array; /* Pointer to the array attribute. */
+
+ /* avoid warnings */
+ (void)opdata;
+
+ /*
+ * Open the attribute using its name.
+ */
+ attr = H5Aopen(loc_id, name, H5P_DEFAULT);
+
+ /*
+ * Display attribute name.
+ */
+ printf("\nName : %s\n", name);
+
+ /*
+ * Get attribute datatype, dataspace, rank, and dimensions.
+ */
+ atype = H5Aget_type(attr);
+ aspace = H5Aget_space(attr);
+ rank = H5Sget_simple_extent_ndims(aspace);
+ ret = H5Sget_simple_extent_dims(aspace, sdim, NULL);
+
+ /*
+ * Display rank and dimension sizes for the array attribute.
+ */
+
+ if (rank > 0) {
+ printf("Rank : %d \n", rank);
+ printf("Dimension sizes : ");
+ for (i = 0; i < rank; i++)
+ printf("%d ", (int)sdim[i]);
+ printf("\n");
+ }
+
+ /*
+ * Read array attribute and display its type and values.
+ */
+
+ if (H5T_FLOAT == H5Tget_class(atype)) {
+ printf("Type : FLOAT \n");
+ npoints = H5Sget_simple_extent_npoints(aspace);
+ float_array = (float *)malloc(sizeof(float) * (int)npoints);
+ ret = H5Aread(attr, atype, float_array);
+ printf("Values : ");
+ for (i = 0; i < (int)npoints; i++)
+ printf("%f ", float_array[i]);
+ printf("\n");
+ free(float_array);
+ }
+
+ /*
+ * Release all identifiers.
+ */
+ H5Tclose(atype);
+ H5Sclose(aspace);
+ H5Aclose(attr);
+
+ return 0;
+}
diff --git a/doxygen/examples/h5_extlink.c b/doxygen/examples/h5_extlink.c
new file mode 100644
index 00000000000..e1f02cf4cfd
--- /dev/null
+++ b/doxygen/examples/h5_extlink.c
@@ -0,0 +1,662 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Copyright by The HDF Group. *
+ * All rights reserved. *
+ * *
+ * This file is part of HDF5. The full HDF5 copyright notice, including *
+ * terms governing use, modification, and redistribution, is contained in *
+ * the COPYING file, which can be found at the root of the source code *
+ * distribution tree, or in https://www.hdfgroup.org/licenses. *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/* This program demonstrates how to create and use "external links" in
+ * HDF5.
+ *
+ * External links point from one HDF5 file to an object (Group, Dataset, or
+ * committed Datatype) in another file.
+ */
+
+#include "hdf5.h"
+#include
+
+#define SOURCE_FILE "extlink_source.h5"
+#define TARGET_FILE "extlink_target.h5"
+
+#define PREFIX_SOURCE_FILE "extlink_prefix_source.h5"
+
+#define SOFT_LINK_FILE "soft_link.h5"
+#define SOFT_LINK_NAME "soft_link_to_group"
+#define UD_SOFT_LINK_NAME "ud_soft_link"
+#define TARGET_GROUP "target_group"
+
+#define UD_SOFT_CLASS 65
+
+#define HARD_LINK_FILE "hard_link.h5"
+#define HARD_LINK_NAME "hard_link_to_group"
+#define UD_HARD_LINK_NAME "ud_hard_link"
+
+#define UD_HARD_CLASS 66
+
+#define PLIST_LINK_PROP "plist_link_prop"
+#define UD_PLIST_CLASS 66
+
+/* Basic external link example
+ *
+ * Creates two files and uses an external link to access an object in the
+ * second file from the first file.
+ */
+static void
+extlink_example(void)
+{
+ hid_t source_file_id, targ_file_id;
+ hid_t group_id, group2_id;
+
+ /* Create two files, a source and a target */
+ source_file_id = H5Fcreate(SOURCE_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+ targ_file_id = H5Fcreate(TARGET_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* Create a group in the target file for the external link to point to. */
+ group_id = H5Gcreate2(targ_file_id, "target_group", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* Close the group and the target file */
+ H5Gclose(group_id);
+
+ /* Create an external link in the source file pointing to the target group.
+ * We could instead have created the external link first, then created the
+ * group it points to; the order doesn't matter.
+ */
+ H5Lcreate_external(TARGET_FILE, "target_group", source_file_id, "ext_link", H5P_DEFAULT, H5P_DEFAULT);
+
+ /* Now we can use the external link to create a new group inside the
+ * target group (even though the target file is closed!). The external
+ * link works just like a soft link.
+ */
+ group_id = H5Gcreate2(source_file_id, "ext_link/new_group", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* The group is inside the target file and we can access it normally.
+ * Here, group_id and group2_id point to the same group inside the
+ * target file.
+ */
+ group2_id = H5Gopen2(targ_file_id, "target_group/new_group", H5P_DEFAULT);
+
+ /* Don't forget to close the IDs we opened. */
+ H5Gclose(group2_id);
+ H5Gclose(group_id);
+
+ H5Fclose(targ_file_id);
+ H5Fclose(source_file_id);
+
+ /* The link from the source file to the target file will work as long as
+ * the target file can be found. If the target file is moved, renamed,
+ * or deleted in the filesystem, HDF5 won't be able to find it and the
+ * external link will "dangle."
+ */
+}
+
+/* External link prefix example
+ *
+ * Uses a group access property list to set a "prefix" for the filenames
+ * accessed through an external link.
+ *
+ * Group access property lists inherit from link access property lists;
+ * the external link prefix property is actually a property of LAPLs.
+ *
+ * This example requires a "red" directory and a "blue" directory to exist
+ * where it is run (so to run this example on Unix, first mkdir red and mkdir
+ * blue).
+ */
+static void
+extlink_prefix_example(void)
+{
+ hid_t source_file_id, red_file_id, blue_file_id;
+ hid_t group_id, group2_id;
+ hid_t gapl_id;
+
+ /* Create three files, a source and two targets. The targets will have
+ * the same name, but one will be located in the red directory and one will
+ * be located in the blue directory */
+ source_file_id = H5Fcreate(PREFIX_SOURCE_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+ red_file_id = H5Fcreate("red/prefix_target.h5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+ blue_file_id = H5Fcreate("blue/prefix_target.h5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* This test needs a red and a blue directory in the filesystem. If they're not present,
+ * trying to create the files above will fail.
+ */
+ if (red_file_id < 0 || blue_file_id < 0)
+ printf("This test requires directories named 'red' and 'blue' to exist. Did you forget to create "
+ "them?\n");
+
+ /* Create an external link in the source file pointing to the root group of
+ * a file named prefix_target.h5. This file doesn't exist in the current
+ * directory, but the files in the red and blue directories both have this
+ * name.
+ */
+ H5Lcreate_external("prefix_target.h5", "/", source_file_id, "ext_link", H5P_DEFAULT, H5P_DEFAULT);
+
+ /* If we tried to traverse the external link now, we would fail (since the
+ * file it points to doesn't exist). Instead, we'll create a group access
+ * property list that will provide a prefix path to the external link.
+ * Group access property lists inherit the properties of link access
+ * property lists.
+ */
+ gapl_id = H5Pcreate(H5P_GROUP_ACCESS);
+ H5Pset_elink_prefix(gapl_id, "red/");
+
+ /* Now if we traverse the external link, HDF5 will look for an external
+ * file named red/prefix_target.h5, which exists.
+ * To pass the group access property list, we need to use H5Gopen2.
+ */
+ group_id = H5Gopen2(source_file_id, "ext_link", gapl_id);
+
+ /* Now we can use the open group ID to create a new group inside the
+ * "red" file.
+ */
+ group2_id = H5Gcreate2(group_id, "pink", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* Close both groups. */
+ H5Gclose(group2_id);
+ H5Gclose(group_id);
+
+ /* If we change the prefix, the same external link can find a file in the blue
+ * directory.
+ */
+ H5Pset_elink_prefix(gapl_id, "blue/");
+ group_id = H5Gopen2(source_file_id, "ext_link", gapl_id);
+ group2_id = H5Gcreate2(group_id, "sky blue", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* Close both groups. */
+ H5Gclose(group2_id);
+ H5Gclose(group_id);
+
+ /* Each file has had a group created inside it using the same external link. */
+ group_id = H5Gopen2(red_file_id, "pink", H5P_DEFAULT);
+ group2_id = H5Gopen2(blue_file_id, "sky blue", H5P_DEFAULT);
+
+ /* Clean up our open IDs */
+ H5Gclose(group2_id);
+ H5Gclose(group_id);
+ H5Pclose(gapl_id);
+ H5Fclose(blue_file_id);
+ H5Fclose(red_file_id);
+ H5Fclose(source_file_id);
+
+ /* User-defined links can expand on the ability to pass in parameters
+ * using an access property list; for instance, a user-defined link
+ * might function like an external link but allow the full filename to be
+ * passed in through the access property list.
+ */
+}
+
+/* Soft Link example
+ *
+ * Create a new class of user-defined links that behave like HDF5's built-in
+ * soft links.
+ *
+ * This isn't very useful by itself (HDF5's soft links already do the same
+ * thing), but it can serve as an example for how to reference objects by
+ * name.
+ */
+
+/* We need to define the callback function that the soft link will use.
+ * It is defined after the example below.
+ * To keep the example simple, these links don't have a query callback.
+ * In general, link classes should always be query-able.
+ * We might also have wanted to supply a creation callback that checks
+ * that a path was supplied in the udata.
+ */
+static hid_t UD_soft_traverse(const char *link_name, hid_t cur_group, const void *udata, size_t udata_size,
+ hid_t lapl_id, hid_t dxpl_id);
+
+static void
+soft_link_example(void)
+{
+ hid_t file_id;
+ hid_t group_id;
+ /* Define the link class that we'll use to register "user-defined soft
+ * links" using the callbacks we defined above.
+ * A link class can have NULL for any callback except its traverse
+ * callback.
+ */
+ const H5L_class_t UD_soft_class[1] = {{
+ H5L_LINK_CLASS_T_VERS, /* Version number for this struct.
+ * This field is always H5L_LINK_CLASS_T_VERS */
+ (H5L_type_t)UD_SOFT_CLASS, /* Link class id number. This can be any
+ * value between H5L_TYPE_UD_MIN (64) and
+ * H5L_TYPE_MAX (255). It should be a
+ * value that isn't already being used by
+ * another kind of link. We'll use 65. */
+ "UD_soft_link", /* Link class name for debugging */
+ NULL, /* Creation callback */
+ NULL, /* Move callback */
+ NULL, /* Copy callback */
+ UD_soft_traverse, /* The actual traversal function */
+ NULL, /* Deletion callback */
+ NULL /* Query callback */
+ }};
+
+ /* First, create a file and an object within the file for the link to
+ * point to.
+ */
+ file_id = H5Fcreate(SOFT_LINK_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+ group_id = H5Gcreate2(file_id, TARGET_GROUP, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+ H5Gclose(group_id);
+
+ /* This is how we create a normal soft link to the group.
+ */
+ H5Lcreate_soft(TARGET_GROUP, file_id, SOFT_LINK_NAME, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* To do the same thing using a user-defined link, we first have to
+ * register the link class we defined.
+ */
+ H5Lregister(UD_soft_class);
+
+ /* Now create a user-defined link. We give it the path to the group
+ * as its udata.1
+ */
+ H5Lcreate_ud(file_id, UD_SOFT_LINK_NAME, (H5L_type_t)UD_SOFT_CLASS, TARGET_GROUP,
+ strlen(TARGET_GROUP) + 1, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* We can access the group through the UD soft link like we would through
+ * a normal soft link. This link will still dangle if the object's
+ * original name is changed or unlinked.
+ */
+ group_id = H5Gopen2(file_id, UD_SOFT_LINK_NAME, H5P_DEFAULT);
+
+ /* The group is now open normally. Don't forget to close it! */
+ H5Gclose(group_id);
+
+ H5Fclose(file_id);
+}
+
+/* UD_soft_traverse
+ * The actual traversal function simply needs to open the correct object by
+ * name and return its ID.
+ */
+
+static hid_t
+UD_soft_traverse(const char *link_name, hid_t cur_group, const void *udata, size_t udata_size, hid_t lapl_id,
+ hid_t dxpl_id)
+{
+ const char *target = (const char *)udata;
+ hid_t ret_value;
+
+ /* Pass the udata straight through to HDF5. If it's invalid, let HDF5
+ * return an error.
+ */
+ ret_value = H5Oopen(cur_group, target, lapl_id);
+ return ret_value;
+}
+
+/* Hard Link example
+ *
+ * Create a new class of user-defined links that behave like HDF5's built-in
+ * hard links.
+ *
+ * This isn't very useful by itself (HDF5's hard links already do the same
+ * thing), but it can serve as an example for how to reference objects by
+ * address.
+ */
+
+/* We need to define the callback functions that the hard link will use.
+ * These are defined after the example below.
+ * To keep the example simple, these links don't have a query callback.
+ * Generally, real link classes should always be query-able.
+ */
+static herr_t UD_hard_create(const char *link_name, hid_t loc_group, const void *udata, size_t udata_size,
+ hid_t lcpl_id);
+static herr_t UD_hard_delete(const char *link_name, hid_t loc_group, const void *udata, size_t udata_size);
+static hid_t UD_hard_traverse(const char *link_name, hid_t cur_group, const void *udata, size_t udata_size,
+ hid_t lapl_id, hid_t dxpl_id);
+
+static void
+hard_link_example(void)
+{
+ hid_t file_id;
+ hid_t group_id;
+ H5L_info2_t li;
+ /* Define the link class that we'll use to register "user-defined hard
+ * links" using the callbacks we defined above.
+ * A link class can have NULL for any callback except its traverse
+ * callback.
+ */
+ const H5L_class_t UD_hard_class[1] = {{
+ H5L_LINK_CLASS_T_VERS, /* Version number for this struct.
+ * This field is always H5L_LINK_CLASS_T_VERS */
+ (H5L_type_t)UD_HARD_CLASS, /* Link class id number. This can be any
+ * value between H5L_TYPE_UD_MIN (64) and
+ * H5L_TYPE_MAX (255). It should be a
+ * value that isn't already being used by
+ * another kind of link. We'll use 66. */
+ "UD_hard_link", /* Link class name for debugging */
+ UD_hard_create, /* Creation callback */
+ NULL, /* Move callback */
+ NULL, /* Copy callback */
+ UD_hard_traverse, /* The actual traversal function */
+ UD_hard_delete, /* Deletion callback */
+ NULL /* Query callback */
+ }};
+
+ /* First, create a file and an object within the file for the link to
+ * point to.
+ */
+ file_id = H5Fcreate(HARD_LINK_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+ group_id = H5Gcreate2(file_id, TARGET_GROUP, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+ H5Gclose(group_id);
+
+ /* This is how we create a normal hard link to the group. This
+ * creates a second "name" for the group.
+ */
+ H5Lcreate_hard(file_id, TARGET_GROUP, file_id, HARD_LINK_NAME, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* To do the same thing using a user-defined link, we first have to
+ * register the link class we defined.
+ */
+ H5Lregister(UD_hard_class);
+
+ /* Since hard links link by object address, we'll need to retrieve
+ * the target group's address. We do this by calling H5Lget_info
+ * on a hard link to the object.
+ */
+ H5Lget_info2(file_id, TARGET_GROUP, &li, H5P_DEFAULT);
+
+ /* Now create a user-defined link. We give it the group's address
+ * as its udata.
+ */
+ H5Lcreate_ud(file_id, UD_HARD_LINK_NAME, (H5L_type_t)UD_HARD_CLASS, &(li.u.token), sizeof(H5O_token_t),
+ H5P_DEFAULT, H5P_DEFAULT);
+
+ /* The UD hard link has now incremented the group's reference count
+ * like a normal hard link would. This means that we can unlink the
+ * other two links to that group and it won't be deleted until the
+ * UD hard link is deleted.
+ */
+ H5Ldelete(file_id, TARGET_GROUP, H5P_DEFAULT);
+ H5Ldelete(file_id, HARD_LINK_NAME, H5P_DEFAULT);
+
+ /* The group is still accessible through the UD hard link. If this were
+ * a soft link instead, the object would have been deleted when the last
+ * hard link to it was unlinked. */
+ group_id = H5Gopen2(file_id, UD_HARD_LINK_NAME, H5P_DEFAULT);
+
+ /* The group is now open normally. Don't forget to close it! */
+ H5Gclose(group_id);
+
+ /* Removing the user-defined hard link will delete the group. */
+ H5Ldelete(file_id, UD_HARD_LINK_NAME, H5P_DEFAULT);
+
+ H5Fclose(file_id);
+}
+
+/* Callbacks for User-defined hard links. */
+/* UD_hard_create
+ * The most important thing this callback does is to increment the reference
+ * count on the target object. Without this step, the object could be
+ * deleted while this link still pointed to it, resulting in possible data
+ * corruption!
+ * The create callback also checks the arguments used to create this link.
+ * If this function returns a negative value, the call to H5Lcreate_ud()
+ * will also return failure and the link will not be created.
+ */
+static herr_t
+UD_hard_create(const char *link_name, hid_t loc_group, const void *udata, size_t udata_size, hid_t lcpl_id)
+{
+ H5O_token_t token;
+ hid_t target_obj = H5I_INVALID_HID;
+ herr_t ret_value = 0;
+
+ /* Make sure that the address passed in looks valid */
+ if (udata_size != sizeof(H5O_token_t)) {
+ ret_value = -1;
+ goto done;
+ }
+
+ token = *((const H5O_token_t *)udata);
+
+ //! [H5Oopen_by_token_snip]
+
+ /* Open the object this link points to so that we can increment
+ * its reference count. This also ensures that the token passed
+ * in points to a real object (although this check is not perfect!) */
+ target_obj = H5Oopen_by_token(loc_group, token);
+
+ //! [H5Oopen_by_token_snip]
+
+ if (target_obj < 0) {
+ ret_value = -1;
+ goto done;
+ }
+
+ /* Increment the reference count of the target object */
+ if (H5Oincr_refcount(target_obj) < 0) {
+ ret_value = -1;
+ goto done;
+ }
+
+done:
+ /* Close the target object if we opened it */
+ if (target_obj >= 0)
+ H5Oclose(target_obj);
+ return ret_value;
+}
+
+/* UD_hard_delete
+ * Since the creation function increments the object's reference count, it's
+ * important to decrement it again when the link is deleted.
+ */
+static herr_t
+UD_hard_delete(const char *link_name, hid_t loc_group, const void *udata, size_t udata_size)
+{
+ H5O_token_t token;
+ hid_t target_obj = H5I_INVALID_HID;
+ herr_t ret_value = 0;
+
+ /* Sanity check; we have already verified the udata's size in the creation
+ * callback.
+ */
+ if (udata_size != sizeof(H5O_token_t)) {
+ ret_value = -1;
+ goto done;
+ }
+
+ token = *((const H5O_token_t *)udata);
+
+ /* Open the object this link points to */
+ target_obj = H5Oopen_by_token(loc_group, token);
+ if (target_obj < 0) {
+ ret_value = -1;
+ goto done;
+ }
+
+ /* Decrement the reference count of the target object */
+ if (H5Odecr_refcount(target_obj) < 0) {
+ ret_value = -1;
+ goto done;
+ }
+
+done:
+ /* Close the target object if we opened it */
+ if (target_obj >= 0)
+ H5Oclose(target_obj);
+ return ret_value;
+}
+
+/* UD_hard_traverse
+ * The actual traversal function simply needs to open the correct object and
+ * return its ID.
+ */
+static hid_t
+UD_hard_traverse(const char *link_name, hid_t cur_group, const void *udata, size_t udata_size, hid_t lapl_id,
+ hid_t dxpl_id)
+{
+ H5O_token_t token;
+ hid_t ret_value = H5I_INVALID_HID;
+
+ /* Sanity check; we have already verified the udata's size in the creation
+ * callback.
+ */
+ if (udata_size != sizeof(H5O_token_t))
+ return H5I_INVALID_HID;
+
+ token = *((const H5O_token_t *)udata);
+
+ /* Open the object by token. If H5Oopen_by_token fails, ret_value will
+ * be negative to indicate that the traversal function failed.
+ */
+ ret_value = H5Oopen_by_token(cur_group, token);
+
+ return ret_value;
+}
+
+/* Plist example
+ *
+ * Create a new class of user-defined links that open objects within a file
+ * based on a value passed in through a link access property list.
+ *
+ * Group, dataset, and datatype access property lists all inherit from link
+ * access property lists, so they can be used instead of LAPLs.
+ */
+
+/* We need to define the callback functions that this link type will use.
+ * These are defined after the example below.
+ * These links have no udata, so they don't need a query function.
+ */
+static hid_t UD_plist_traverse(const char *link_name, hid_t cur_group, const void *udata, size_t udata_size,
+ hid_t lapl_id, hid_t dxpl_id);
+
+static void
+plist_link_example(void)
+{
+ hid_t file_id;
+ hid_t group_id, group2_id;
+ hid_t gapl_id;
+ char *path = NULL;
+
+ /* Define the link class that we'll use to register "plist
+ * links" using the callback we defined above.
+ * A link class can have NULL for any callback except its traverse
+ * callback.
+ */
+ const H5L_class_t UD_plist_class[1] = {{
+ H5L_LINK_CLASS_T_VERS, /* Version number for this struct.
+ * This field is always H5L_LINK_CLASS_T_VERS */
+ (H5L_type_t)UD_PLIST_CLASS, /* Link class id number. This can be any
+ * value between H5L_TYPE_UD_MIN (64) and
+ * H5L_TYPE_MAX (255). It should be a
+ * value that isn't already being used by
+ * another kind of link. We'll use 67. */
+ "UD_plist_link", /* Link class name for debugging */
+ NULL, /* Creation callback */
+ NULL, /* Move callback */
+ NULL, /* Copy callback */
+ UD_plist_traverse, /* The actual traversal function */
+ NULL, /* Deletion callback */
+ NULL /* Query callback */
+ }};
+
+ /* First, create a file and two objects within the file for the link to
+ * point to.
+ */
+ file_id = H5Fcreate(HARD_LINK_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+ group_id = H5Gcreate2(file_id, "group_1", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+ H5Gclose(group_id);
+ group_id = H5Gcreate2(file_id, "group_1/group_2", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+ H5Gclose(group_id);
+
+ /* Register "plist links" and create one. It has no udata at all. */
+ H5Lregister(UD_plist_class);
+ H5Lcreate_ud(file_id, "plist_link", (H5L_type_t)UD_PLIST_CLASS, NULL, 0, H5P_DEFAULT, H5P_DEFAULT);
+
+ /* Create a group access property list to pass in the target for the
+ * plist link.
+ */
+ gapl_id = H5Pcreate(H5P_GROUP_ACCESS);
+
+ /* There is no HDF5 API for setting the property that controls these
+ * links, so we have to add the property manually
+ */
+ H5Pinsert2(gapl_id, PLIST_LINK_PROP, sizeof(const char *), &(path), NULL, NULL, NULL, NULL, NULL, NULL);
+
+ /* Set the property to point to the first group. */
+ path = "group_1";
+ H5Pset(gapl_id, PLIST_LINK_PROP, &path);
+
+ /* Open the first group through the plist link using the GAPL we just
+ * created */
+ group_id = H5Gopen2(file_id, "plist_link", gapl_id);
+
+ /* If we change the value set on the property list, it will change where
+ * the plist link points.
+ */
+ path = "group_1/group_2";
+ H5Pset(gapl_id, PLIST_LINK_PROP, &path);
+ group2_id = H5Gopen2(file_id, "plist_link", gapl_id);
+
+ /* group_id points to group_1 and group2_id points to group_2, both opened
+ * through the same link.
+ * Using more than one of this type of link could quickly become confusing,
+ * since they will all use the same property list; however, there is
+ * nothing to prevent the links from changing the property list in their
+ * traverse callbacks.
+ */
+
+ /* Clean up */
+ H5Pclose(gapl_id);
+ H5Gclose(group_id);
+ H5Gclose(group2_id);
+ H5Fclose(file_id);
+}
+
+/* Traversal callback for User-defined plist links. */
+/* UD_plist_traverse
+ * Open a path passed in through the property list.
+ */
+static hid_t
+UD_plist_traverse(const char *link_name, hid_t cur_group, const void *udata, size_t udata_size, hid_t lapl_id,
+ hid_t dxpl_id)
+{
+ char *path;
+ hid_t ret_value = H5I_INVALID_HID;
+
+ /* If the link property isn't set or can't be found, traversal fails. */
+ if (H5Pexist(lapl_id, PLIST_LINK_PROP) < 0)
+ goto error;
+
+ if (H5Pget(lapl_id, PLIST_LINK_PROP, &path) < 0)
+ goto error;
+
+ /* Open the object by address. If H5Oopen_by_addr fails, ret_value will
+ * be negative to indicate that the traversal function failed.
+ */
+ ret_value = H5Oopen(cur_group, path, lapl_id);
+
+ return ret_value;
+
+error:
+ return H5I_INVALID_HID;
+}
+
+/* Main function
+ *
+ * Invokes the example functions.
+ */
+int
+main(void)
+{
+ printf("Testing basic external links.\n");
+ extlink_example();
+
+ printf("Testing external link prefixes.\n");
+ extlink_prefix_example();
+
+ printf("Testing user-defined soft links.\n");
+ soft_link_example();
+
+ printf("Testing user-defined hard links.\n");
+ hard_link_example();
+
+ printf("Testing user-defined property list links.\n");
+ plist_link_example();
+
+ return 0;
+}