From 4c791dbf59721ff6049da92fd3a7e2db51927715 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Mon, 8 Jun 2015 15:38:53 +0100 Subject: [PATCH] Add .gitattributes. Sanitise EOL whitespace and line endings. --- .gitattributes | 13 + Com.Adobe.Xmp/Com/adobe/xmp/XMPConst.cs | 426 +- Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTime.cs | 232 +- .../Com/adobe/xmp/XMPDateTimeFactory.cs | 322 +- Com.Adobe.Xmp/Com/adobe/xmp/XMPError.cs | 92 +- Com.Adobe.Xmp/Com/adobe/xmp/XMPException.cs | 94 +- Com.Adobe.Xmp/Com/adobe/xmp/XMPIterator.cs | 162 +- Com.Adobe.Xmp/Com/adobe/xmp/XMPMeta.cs | 2374 +++++------ Com.Adobe.Xmp/Com/adobe/xmp/XMPMetaFactory.cs | 624 +-- Com.Adobe.Xmp/Com/adobe/xmp/XMPPathFactory.cs | 572 +-- .../Com/adobe/xmp/XMPSchemaRegistry.cs | 330 +- Com.Adobe.Xmp/Com/adobe/xmp/XMPUtils.cs | 866 ++-- Com.Adobe.Xmp/Com/adobe/xmp/XMPVersionInfo.cs | 86 +- Com.Adobe.Xmp/Com/adobe/xmp/impl/Base64.cs | 486 +-- .../Com/adobe/xmp/impl/ByteBuffer.cs | 582 +-- .../Com/adobe/xmp/impl/CountOutputStream.cs | 128 +- .../adobe/xmp/impl/FixASCIIControlsReader.cs | 464 +-- .../Com/adobe/xmp/impl/ISO8601Converter.cs | 882 ++--- .../Com/adobe/xmp/impl/Latin1Converter.cs | 378 +- .../Com/adobe/xmp/impl/ParameterAsserts.cs | 256 +- Com.Adobe.Xmp/Com/adobe/xmp/impl/ParseRDF.cs | 2644 ++++++------- Com.Adobe.Xmp/Com/adobe/xmp/impl/QName.cs | 134 +- Com.Adobe.Xmp/Com/adobe/xmp/impl/Utils.cs | 1072 ++--- .../Com/adobe/xmp/impl/XMPDateTimeImpl.cs | 630 +-- .../Com/adobe/xmp/impl/XMPIteratorImpl.cs | 1136 +++--- .../Com/adobe/xmp/impl/XMPMetaImpl.cs | 2352 +++++------ .../Com/adobe/xmp/impl/XMPMetaParser.cs | 690 ++-- Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNode.cs | 1528 +++---- .../Com/adobe/xmp/impl/XMPNodeUtils.cs | 1744 ++++---- .../Com/adobe/xmp/impl/XMPNormalizer.cs | 1202 +++--- .../adobe/xmp/impl/XMPSchemaRegistryImpl.cs | 950 ++--- .../Com/adobe/xmp/impl/XMPSerializerHelper.cs | 184 +- .../Com/adobe/xmp/impl/XMPSerializerRDF.cs | 2456 ++++++------ .../Com/adobe/xmp/impl/XMPUtilsImpl.cs | 2210 +++++------ .../Com/adobe/xmp/impl/xpath/XMPPath.cs | 184 +- .../Com/adobe/xmp/impl/xpath/XMPPathParser.cs | 902 ++--- .../adobe/xmp/impl/xpath/XMPPathSegment.cs | 238 +- .../Com/adobe/xmp/options/AliasOptions.cs | 322 +- .../Com/adobe/xmp/options/IteratorOptions.cs | 268 +- .../Com/adobe/xmp/options/Options.cs | 480 +-- .../Com/adobe/xmp/options/ParseOptions.cs | 308 +- .../Com/adobe/xmp/options/PropertyOptions.cs | 750 ++-- .../Com/adobe/xmp/options/SerializeOptions.cs | 870 ++-- .../Com/adobe/xmp/properties/XMPAliasInfo.cs | 72 +- .../Com/adobe/xmp/properties/XMPProperty.cs | 64 +- .../adobe/xmp/properties/XMPPropertyInfo.cs | 68 +- Com.Adobe.Xmp/Properties/AssemblyInfo.cs | 10 +- .../imaging/jpeg/JpegMetadataReaderTest.cs | 106 +- .../drew/imaging/jpeg/JpegSegmentDataTest.cs | 244 +- .../imaging/jpeg/JpegSegmentReaderTest.cs | 252 +- .../drew/imaging/png/PngChunkReaderTest.cs | 136 +- .../Com/drew/imaging/png/PngChunkTypeTest.cs | 246 +- .../drew/imaging/png/PngMetadataReaderTest.cs | 178 +- .../Com/drew/lang/ByteArrayReaderTest.cs | 76 +- Com.Drew.Tests/Com/drew/lang/ByteTrieTest.cs | 104 +- .../Com/drew/lang/CompoundExceptionTest.cs | 188 +- .../Com/drew/lang/GeoLocationTest.cs | 98 +- .../Com/drew/lang/NullOutputStreamTest.cs | 92 +- .../drew/lang/RandomAccessFileReaderTest.cs | 160 +- .../drew/lang/RandomAccessStreamReaderTest.cs | 80 +- .../Com/drew/lang/RandomAccessTestBase.cs | 692 ++-- Com.Drew.Tests/Com/drew/lang/RationalTest.cs | 210 +- .../Com/drew/lang/SequentialAccessTestBase.cs | 704 ++-- .../lang/SequentialByteArrayReaderTest.cs | 32 +- .../Com/drew/lang/StreamReaderTest.cs | 36 +- .../Com/drew/lang/StringUtilTest.cs | 102 +- Com.Drew.Tests/Com/drew/metadata/AgeTest.cs | 128 +- .../Com/drew/metadata/DirectoryTest.cs | 374 +- .../Com/drew/metadata/MetadataTest.cs | 122 +- .../Com/drew/metadata/MockDirectory.cs | 102 +- .../metadata/adobe/AdobeJpegReaderTest.cs | 134 +- .../Com/drew/metadata/bmp/BmpReaderTest.cs | 162 +- .../exif/CanonMakernoteDescriptorTest.cs | 122 +- .../drew/metadata/exif/ExifDirectoryTest.cs | 246 +- .../metadata/exif/ExifIFD0DescriptorTest.cs | 158 +- .../exif/ExifInteropDescriptorTest.cs | 116 +- .../Com/drew/metadata/exif/ExifReaderTest.cs | 472 +-- .../metadata/exif/ExifSubIFDDescriptorTest.cs | 230 +- .../exif/ExifThumbnailDescriptorTest.cs | 92 +- .../metadata/exif/NikonType1MakernoteTest.cs | 364 +- .../metadata/exif/NikonType2MakernoteTest1.cs | 302 +- .../metadata/exif/NikonType2MakernoteTest2.cs | 384 +- .../exif/PanasonicMakernoteDescriptorTest.cs | 122 +- .../metadata/exif/SonyType1MakernoteTest.cs | 116 +- .../metadata/exif/SonyType6MakernoteTest.cs | 84 +- .../Com/drew/metadata/gif/GifReaderTest.cs | 154 +- .../Com/drew/metadata/icc/IccReaderTest.cs | 98 +- .../Com/drew/metadata/iptc/IptcReaderTest.cs | 400 +- .../metadata/iptc/Iso2022ConverterTest.cs | 32 +- .../Com/drew/metadata/jfif/JfifReaderTest.cs | 110 +- .../drew/metadata/jpeg/JpegComponentTest.cs | 96 +- .../drew/metadata/jpeg/JpegDescriptorTest.cs | 174 +- .../drew/metadata/jpeg/JpegDirectoryTest.cs | 196 +- .../Com/drew/metadata/jpeg/JpegReaderTest.cs | 342 +- .../drew/metadata/photoshop/PsdReaderTest.cs | 160 +- .../Com/drew/metadata/xmp/XmpReaderTest.cs | 452 +-- Com.Drew.Tests/Com/drew/testing/TestHelper.cs | 82 +- Com.Drew.Tests/Properties/AssemblyInfo.cs | 10 +- Com.Drew/Com/drew/imaging/FileType.cs | 92 +- Com.Drew/Com/drew/imaging/FileTypeDetector.cs | 208 +- .../Com/drew/imaging/ImageMetadataReader.cs | 698 ++-- .../drew/imaging/ImageProcessingException.cs | 102 +- .../drew/imaging/PhotographicConversions.cs | 114 +- .../Com/drew/imaging/bmp/BmpMetadataReader.cs | 122 +- .../Com/drew/imaging/gif/GifMetadataReader.cs | 122 +- .../Com/drew/imaging/ico/IcoMetadataReader.cs | 122 +- .../drew/imaging/jpeg/JpegMetadataReader.cs | 274 +- .../imaging/jpeg/JpegProcessingException.cs | 100 +- .../Com/drew/imaging/jpeg/JpegSegmentData.cs | 476 +-- .../imaging/jpeg/JpegSegmentMetadataReader.cs | 60 +- .../drew/imaging/jpeg/JpegSegmentReader.cs | 388 +- .../Com/drew/imaging/jpeg/JpegSegmentType.cs | 370 +- .../Com/drew/imaging/pcx/PcxMetadataReader.cs | 122 +- .../Com/drew/imaging/png/PngChromaticities.cs | 182 +- Com.Drew/Com/drew/imaging/png/PngChunk.cs | 64 +- .../Com/drew/imaging/png/PngChunkReader.cs | 198 +- Com.Drew/Com/drew/imaging/png/PngChunkType.cs | 464 +-- Com.Drew/Com/drew/imaging/png/PngColorType.cs | 146 +- Com.Drew/Com/drew/imaging/png/PngHeader.cs | 184 +- .../Com/drew/imaging/png/PngMetadataReader.cs | 596 +-- .../imaging/png/PngProcessingException.cs | 100 +- .../Com/drew/imaging/psd/PsdMetadataReader.cs | 122 +- Com.Drew/Com/drew/imaging/riff/RiffHandler.cs | 156 +- .../imaging/riff/RiffProcessingException.cs | 100 +- Com.Drew/Com/drew/imaging/riff/RiffReader.cs | 220 +- .../Com/drew/imaging/tiff/TiffDataFormat.cs | 378 +- Com.Drew/Com/drew/imaging/tiff/TiffHandler.cs | 208 +- .../drew/imaging/tiff/TiffMetadataReader.cs | 148 +- .../imaging/tiff/TiffProcessingException.cs | 102 +- Com.Drew/Com/drew/imaging/tiff/TiffReader.cs | 966 ++--- .../drew/imaging/webp/WebpMetadataReader.cs | 130 +- .../Com/drew/lang/BufferBoundsException.cs | 138 +- Com.Drew/Com/drew/lang/ByteArrayReader.cs | 180 +- Com.Drew/Com/drew/lang/ByteTrie.cs | 250 +- Com.Drew/Com/drew/lang/CompoundException.cs | 230 +- Com.Drew/Com/drew/lang/GeoLocation.cs | 348 +- Com.Drew/Com/drew/lang/Iterables.cs | 58 +- Com.Drew/Com/drew/lang/KeyValuePair.cs | 70 +- Com.Drew/Com/drew/lang/NullOutputStream.cs | 86 +- .../Com/drew/lang/RandomAccessFileReader.cs | 248 +- Com.Drew/Com/drew/lang/RandomAccessReader.cs | 752 ++-- .../Com/drew/lang/RandomAccessStreamReader.cs | 408 +- Com.Drew/Com/drew/lang/Rational.cs | 676 ++-- .../drew/lang/SequentialByteArrayReader.cs | 212 +- Com.Drew/Com/drew/lang/SequentialReader.cs | 546 +-- Com.Drew/Com/drew/lang/StreamReader.cs | 244 +- Com.Drew/Com/drew/lang/StringUtil.cs | 266 +- Com.Drew/Com/drew/metadata/Age.cs | 414 +- .../Com/drew/metadata/DefaultTagDescriptor.cs | 112 +- Com.Drew/Com/drew/metadata/Directory.cs | 2196 +++++----- Com.Drew/Com/drew/metadata/Face.cs | 328 +- Com.Drew/Com/drew/metadata/Metadata.cs | 464 +-- .../Com/drew/metadata/MetadataException.cs | 102 +- Com.Drew/Com/drew/metadata/MetadataReader.cs | 110 +- Com.Drew/Com/drew/metadata/Schema.cs | 54 +- Com.Drew/Com/drew/metadata/Tag.cs | 290 +- Com.Drew/Com/drew/metadata/TagDescriptor.cs | 652 +-- .../metadata/adobe/AdobeJpegDescriptor.cs | 192 +- .../drew/metadata/adobe/AdobeJpegDirectory.cs | 160 +- .../drew/metadata/adobe/AdobeJpegReader.cs | 156 +- .../drew/metadata/bmp/BmpHeaderDescriptor.cs | 202 +- .../drew/metadata/bmp/BmpHeaderDirectory.cs | 128 +- Com.Drew/Com/drew/metadata/bmp/BmpReader.cs | 216 +- .../drew/metadata/exif/ExifDescriptorBase.cs | 3128 +++++++-------- .../drew/metadata/exif/ExifDirectoryBase.cs | 1830 ++++----- .../drew/metadata/exif/ExifIFD0Descriptor.cs | 80 +- .../drew/metadata/exif/ExifIFD0Directory.cs | 126 +- .../metadata/exif/ExifInteropDescriptor.cs | 80 +- .../metadata/exif/ExifInteropDirectory.cs | 114 +- Com.Drew/Com/drew/metadata/exif/ExifReader.cs | 240 +- .../metadata/exif/ExifSubIFDDescriptor.cs | 80 +- .../drew/metadata/exif/ExifSubIFDDirectory.cs | 120 +- .../metadata/exif/ExifThumbnailDescriptor.cs | 466 +-- .../metadata/exif/ExifThumbnailDirectory.cs | 664 ++-- .../Com/drew/metadata/exif/ExifTiffHandler.cs | 956 ++--- .../Com/drew/metadata/exif/GpsDescriptor.cs | 666 ++-- .../Com/drew/metadata/exif/GpsDirectory.cs | 430 +- .../makernotes/CanonMakernoteDescriptor.cs | 1784 ++++----- .../makernotes/CanonMakernoteDirectory.cs | 1796 ++++----- .../CasioType1MakernoteDescriptor.cs | 660 +-- .../CasioType1MakernoteDirectory.cs | 244 +- .../CasioType2MakernoteDescriptor.cs | 1014 ++--- .../CasioType2MakernoteDirectory.cs | 444 +-- .../makernotes/FujifilmMakernoteDescriptor.cs | 1976 ++++----- .../makernotes/FujifilmMakernoteDirectory.cs | 380 +- .../makernotes/KodakMakernoteDescriptor.cs | 520 +-- .../makernotes/KodakMakernoteDirectory.cs | 270 +- .../makernotes/KyoceraMakernoteDescriptor.cs | 162 +- .../makernotes/KyoceraMakernoteDirectory.cs | 126 +- .../makernotes/LeicaMakernoteDescriptor.cs | 280 +- .../makernotes/LeicaMakernoteDirectory.cs | 232 +- .../NikonType1MakernoteDescriptor.cs | 308 +- .../NikonType1MakernoteDirectory.cs | 202 +- .../NikonType2MakernoteDescriptor.cs | 912 ++--- .../NikonType2MakernoteDirectory.cs | 1980 ++++----- .../makernotes/OlympusMakernoteDescriptor.cs | 1844 ++++----- .../makernotes/OlympusMakernoteDirectory.cs | 1194 +++--- .../PanasonicMakernoteDescriptor.cs | 1776 ++++----- .../makernotes/PanasonicMakernoteDirectory.cs | 1282 +++--- .../makernotes/PentaxMakernoteDescriptor.cs | 436 +- .../makernotes/PentaxMakernoteDirectory.cs | 324 +- .../makernotes/RicohMakernoteDescriptor.cs | 140 +- .../makernotes/RicohMakernoteDirectory.cs | 138 +- .../makernotes/SanyoMakernoteDescriptor.cs | 762 ++-- .../makernotes/SanyoMakernoteDirectory.cs | 276 +- .../makernotes/SigmaMakernoteDescriptor.cs | 252 +- .../makernotes/SigmaMakernoteDirectory.cs | 252 +- .../SonyType1MakernoteDescriptor.cs | 3524 ++++++++--------- .../makernotes/SonyType1MakernoteDirectory.cs | 530 +-- .../SonyType6MakernoteDescriptor.cs | 126 +- .../makernotes/SonyType6MakernoteDirectory.cs | 136 +- .../metadata/file/FileMetadataDescriptor.cs | 128 +- .../metadata/file/FileMetadataDirectory.cs | 130 +- .../drew/metadata/file/FileMetadataReader.cs | 62 +- .../drew/metadata/gif/GifHeaderDescriptor.cs | 46 +- .../drew/metadata/gif/GifHeaderDirectory.cs | 122 +- Com.Drew/Com/drew/metadata/gif/GifReader.cs | 158 +- .../Com/drew/metadata/icc/IccDescriptor.cs | 1064 ++--- .../Com/drew/metadata/icc/IccDirectory.cs | 508 +-- Com.Drew/Com/drew/metadata/icc/IccReader.cs | 394 +- .../Com/drew/metadata/ico/IcoDescriptor.cs | 210 +- .../Com/drew/metadata/ico/IcoDirectory.cs | 172 +- Com.Drew/Com/drew/metadata/ico/IcoReader.cs | 234 +- .../Com/drew/metadata/iptc/IptcDescriptor.cs | 824 ++-- .../Com/drew/metadata/iptc/IptcDirectory.cs | 760 ++-- Com.Drew/Com/drew/metadata/iptc/IptcReader.cs | 564 +-- .../drew/metadata/iptc/Iso2022Converter.cs | 168 +- .../Com/drew/metadata/jfif/JfifDescriptor.cs | 278 +- .../Com/drew/metadata/jfif/JfifDirectory.cs | 192 +- Com.Drew/Com/drew/metadata/jfif/JfifReader.cs | 176 +- .../metadata/jpeg/JpegCommentDescriptor.cs | 92 +- .../metadata/jpeg/JpegCommentDirectory.cs | 130 +- .../drew/metadata/jpeg/JpegCommentReader.cs | 124 +- .../Com/drew/metadata/jpeg/JpegComponent.cs | 212 +- .../Com/drew/metadata/jpeg/JpegDescriptor.cs | 448 +-- .../Com/drew/metadata/jpeg/JpegDirectory.cs | 290 +- Com.Drew/Com/drew/metadata/jpeg/JpegReader.cs | 186 +- .../Com/drew/metadata/pcx/PcxDescriptor.cs | 164 +- .../Com/drew/metadata/pcx/PcxDirectory.cs | 196 +- Com.Drew/Com/drew/metadata/pcx/PcxReader.cs | 186 +- .../metadata/photoshop/PhotoshopDescriptor.cs | 912 ++--- .../metadata/photoshop/PhotoshopDirectory.cs | 654 +-- .../metadata/photoshop/PhotoshopReader.cs | 346 +- .../metadata/photoshop/PsdHeaderDescriptor.cs | 422 +- .../metadata/photoshop/PsdHeaderDirectory.cs | 166 +- .../Com/drew/metadata/photoshop/PsdReader.cs | 220 +- .../png/PngChromaticitiesDirectory.cs | 160 +- .../Com/drew/metadata/png/PngDescriptor.cs | 422 +- .../Com/drew/metadata/png/PngDirectory.cs | 246 +- .../metadata/tiff/DirectoryTiffHandler.cs | 440 +- .../Com/drew/metadata/webp/WebpDescriptor.cs | 94 +- .../Com/drew/metadata/webp/WebpDirectory.cs | 136 +- .../Com/drew/metadata/webp/WebpRiffHandler.cs | 242 +- .../Com/drew/metadata/xmp/XmpDescriptor.cs | 482 +-- .../Com/drew/metadata/xmp/XmpDirectory.cs | 1018 ++--- Com.Drew/Com/drew/metadata/xmp/XmpReader.cs | 648 +-- Com.Drew/Com/drew/metadata/xmp/XmpWriter.cs | 68 +- .../Com/drew/tools/ExtractJpegSegmentTool.cs | 252 +- Com.Drew/Com/drew/tools/FileUtil.cs | 230 +- .../tools/ProcessAllImagesInFolderUtility.cs | 1234 +++--- Com.Drew/Com/drew/tools/ProcessUrlUtility.cs | 224 +- Com.Drew/Properties/AssemblyInfo.cs | 10 +- FileLabeller/App.config | 10 +- FileLabeller/BasicFileHandler.cs | 54 +- FileLabeller/FileHandlerBase.cs | 140 +- FileLabeller/IFileHandler.cs | 78 +- FileLabeller/Program.cs | 262 +- FileLabeller/Properties/AssemblyInfo.cs | 70 +- FileLabeller/TextFileOutputHandler.cs | 250 +- SampleReader/App.config | 10 +- SampleReader/Program.cs | 243 +- SampleReader/Properties/AssemblyInfo.cs | 70 +- Sharpen.Tests/Properties/AssemblyInfo.cs | 11 +- Sharpen.Tests/StringsExtractor.ps1 | 2 +- Sharpen/AssemblyInfo.cs | 16 +- Sharpen/Extra/JetBrainsAnnotations.cs | 50 +- Sharpen/Sharpen/AList.cs | 26 +- Sharpen/Sharpen/AbstractList.cs | 6 +- Sharpen/Sharpen/AbstractSet.cs | 4 +- Sharpen/Sharpen/AtomicBoolean.cs | 20 +- Sharpen/Sharpen/AtomicInteger.cs | 4 +- Sharpen/Sharpen/AtomicReferenceArray.cs | 2 +- Sharpen/Sharpen/BitSet.cs | 20 +- Sharpen/Sharpen/BufferedWriter.cs | 20 +- Sharpen/Sharpen/ByteArrayInputStream.cs | 2 +- Sharpen/Sharpen/ByteArrayOutputStream.cs | 6 +- Sharpen/Sharpen/Channels.cs | 12 +- Sharpen/Sharpen/CharSequence.cs | 8 +- Sharpen/Sharpen/Collections.cs | 18 +- Sharpen/Sharpen/DataConverter.cs | 246 +- Sharpen/Sharpen/DateFormat.cs | 12 +- Sharpen/Sharpen/DeflaterOutputStream.cs | 2 +- Sharpen/Sharpen/DigestInputStream.cs | 4 +- Sharpen/Sharpen/EnumSet.cs | 6 +- Sharpen/Sharpen/EnumeratorWrapper.cs | 4 +- Sharpen/Sharpen/Exceptions.cs | 22 +- Sharpen/Sharpen/Executors.cs | 38 +- Sharpen/Sharpen/Extensions.cs | 38 +- Sharpen/Sharpen/FileChannel.cs | 6 +- Sharpen/Sharpen/FileHelper.cs | 12 +- Sharpen/Sharpen/FilePath.cs | 18 +- Sharpen/Sharpen/GregorianCalendar.cs | 2 +- Sharpen/Sharpen/HttpURLConnection.cs | 64 +- Sharpen/Sharpen/InputStream.cs | 12 +- Sharpen/Sharpen/Iterable.cs | 2 +- Sharpen/Sharpen/Iterator.cs | 2 +- Sharpen/Sharpen/LinkedHashMap.cs | 4 +- Sharpen/Sharpen/LinkedHashSet.cs | 28 +- Sharpen/Sharpen/ListIterator.cs | 4 +- Sharpen/Sharpen/MessageDigest.cs | 2 +- Sharpen/Sharpen/MessageFormat.cs | 10 +- Sharpen/Sharpen/Number.cs | 2 +- Sharpen/Sharpen/OutputStream.cs | 2 +- Sharpen/Sharpen/PipedInputStream.cs | 14 +- Sharpen/Sharpen/PrintWriter.cs | 80 +- Sharpen/Sharpen/Process.cs | 10 +- Sharpen/Sharpen/Proxy.cs | 12 +- Sharpen/Sharpen/ProxySelector.cs | 16 +- Sharpen/Sharpen/RandomAccessFile.cs | 6 +- Sharpen/Sharpen/Runtime.cs | 26 +- Sharpen/Sharpen/SSLContext.cs | 16 +- .../Sharpen/ScheduledThreadPoolExecutor.cs | 48 +- Sharpen/Sharpen/SimpleDateFormat.cs | 24 +- Sharpen/Sharpen/Thread.cs | 18 +- Sharpen/Sharpen/ThreadPoolExecutor.cs | 34 +- Sharpen/Sharpen/TreeSet.cs | 2 +- Sharpen/Sharpen/TrustManager.cs | 12 +- Sharpen/Sharpen/URLEncoder.cs | 12 +- Sharpen/Sharpen/WeakReference.cs | 2 +- Sharpen/Sharpen/WrappedSystemStream.cs | 10 +- Sharpen/Sharpen/X509Certificate.cs | 12 +- Sharpen/Sharpen/X509TrustManager.cs | 12 +- 332 files changed, 57735 insertions(+), 57724 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f5d8fb6cb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +* text=auto + +*.cs text +*.csproj text +*.md text +*.sln text + +*.bmp binary +*.bytes binary +*.gif binary +*.jpg binary +*.png binary +*.psd binary diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPConst.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPConst.cs index f4fc4eebd..a1a93edab 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPConst.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPConst.cs @@ -1,213 +1,213 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -namespace Com.Adobe.Xmp -{ - /// Common constants for the XMP Toolkit. - /// 20.01.2006 - public interface XMPConst - { - // --------------------------------------------------------------------------------------------- - // Standard namespace URI constants - // Standard namespaces - // Adobe standard namespaces - // XMP namespaces that are Adobe private - // XML namespace constants for qualifiers and structured property fields. - // --------------------------------------------------------------------------------------------- - // Basic types and constants - } - - public static class XMPConstConstants - { - /// The XML namespace for XML. - public const string NsXml = "http://www.w3.org/XML/1998/namespace"; - - /// The XML namespace for RDF. - public const string NsRdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - - /// The XML namespace for the Dublin Core schema. - public const string NsDc = "http://purl.org/dc/elements/1.1/"; - - /// The XML namespace for the IPTC Core schema. - public const string NsIptccore = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"; - - /// The XML namespace for the IPTC Extension schema. - public const string NsIptcext = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/"; - - /// The XML namespace for the DICOM medical schema. - public const string NsDicom = "http://ns.adobe.com/DICOM/"; - - /// The XML namespace for the PLUS (Picture Licensing Universal System, http://www.useplus.org) - public const string NsPlus = "http://ns.useplus.org/ldf/xmp/1.0/"; - - /// The XML namespace Adobe XMP Metadata. - public const string NsX = "adobe:ns:meta/"; - - public const string NsIx = "http://ns.adobe.com/iX/1.0/"; - - /// The XML namespace for the XMP "basic" schema. - public const string NsXmp = "http://ns.adobe.com/xap/1.0/"; - - /// The XML namespace for the XMP copyright schema. - public const string NsXmpRights = "http://ns.adobe.com/xap/1.0/rights/"; - - /// The XML namespace for the XMP digital asset management schema. - public const string NsXmpMm = "http://ns.adobe.com/xap/1.0/mm/"; - - /// The XML namespace for the job management schema. - public const string NsXmpBj = "http://ns.adobe.com/xap/1.0/bj/"; - - /// The XML namespace for the job management schema. - public const string NsXmpNote = "http://ns.adobe.com/xmp/note/"; - - /// The XML namespace for the PDF schema. - public const string NsPdf = "http://ns.adobe.com/pdf/1.3/"; - - /// The XML namespace for the PDF schema. - public const string NsPdfx = "http://ns.adobe.com/pdfx/1.3/"; - - public const string NsPdfxId = "http://www.npes.org/pdfx/ns/id/"; - - public const string NsPdfaSchema = "http://www.aiim.org/pdfa/ns/schema#"; - - public const string NsPdfaProperty = "http://www.aiim.org/pdfa/ns/property#"; - - public const string NsPdfaType = "http://www.aiim.org/pdfa/ns/type#"; - - public const string NsPdfaField = "http://www.aiim.org/pdfa/ns/field#"; - - public const string NsPdfaId = "http://www.aiim.org/pdfa/ns/id/"; - - public const string NsPdfaExtension = "http://www.aiim.org/pdfa/ns/extension/"; - - /// The XML namespace for the Photoshop custom schema. - public const string NsPhotoshop = "http://ns.adobe.com/photoshop/1.0/"; - - /// The XML namespace for the Photoshop Album schema. - public const string NsPsalbum = "http://ns.adobe.com/album/1.0/"; - - /// The XML namespace for Adobe's EXIF schema. - public const string NsExif = "http://ns.adobe.com/exif/1.0/"; - - /// NS for the CIPA XMP for Exif document v1.1 - public const string NsExifx = "http://cipa.jp/exif/1.0/"; - - public const string NsExifAux = "http://ns.adobe.com/exif/1.0/aux/"; - - /// The XML namespace for Adobe's TIFF schema. - public const string NsTiff = "http://ns.adobe.com/tiff/1.0/"; - - public const string NsPng = "http://ns.adobe.com/png/1.0/"; - - public const string NsJpeg = "http://ns.adobe.com/jpeg/1.0/"; - - public const string NsJp2k = "http://ns.adobe.com/jp2k/1.0/"; - - public const string NsCameraraw = "http://ns.adobe.com/camera-raw-settings/1.0/"; - - public const string NsAdobestockphoto = "http://ns.adobe.com/StockPhoto/1.0/"; - - public const string NsCreatorAtom = "http://ns.adobe.com/creatorAtom/1.0/"; - - public const string NsAsf = "http://ns.adobe.com/asf/1.0/"; - - public const string NsWav = "http://ns.adobe.com/xmp/wav/1.0/"; - - /// BExt Schema - public const string NsBwf = "http://ns.adobe.com/bwf/bext/1.0/"; - - /// RIFF Info Schema - public const string NsRiffinfo = "http://ns.adobe.com/riff/info/"; - - public const string NsScript = "http://ns.adobe.com/xmp/1.0/Script/"; - - /// Transform XMP - public const string NsTxmp = "http://ns.adobe.com/TransformXMP/"; - - /// Adobe Flash SWF - public const string NsSwf = "http://ns.adobe.com/swf/1.0/"; - - public const string NsDm = "http://ns.adobe.com/xmp/1.0/DynamicMedia/"; - - public const string NsTransient = "http://ns.adobe.com/xmp/transient/1.0/"; - - /// legacy Dublin Core NS, will be converted to NS_DC - public const string NsDcDeprecated = "http://purl.org/dc/1.1/"; - - /// The XML namespace for qualifiers of the xmp:Identifier property. - public const string TypeIdentifierqual = "http://ns.adobe.com/xmp/Identifier/qual/1.0/"; - - /// The XML namespace for fields of the Dimensions type. - public const string TypeDimensions = "http://ns.adobe.com/xap/1.0/sType/Dimensions#"; - - public const string TypeText = "http://ns.adobe.com/xap/1.0/t/"; - - public const string TypePagedfile = "http://ns.adobe.com/xap/1.0/t/pg/"; - - public const string TypeGraphics = "http://ns.adobe.com/xap/1.0/g/"; - - /// The XML namespace for fields of a graphical image. - /// The XML namespace for fields of a graphical image. Used for the Thumbnail type. - public const string TypeImage = "http://ns.adobe.com/xap/1.0/g/img/"; - - public const string TypeFont = "http://ns.adobe.com/xap/1.0/sType/Font#"; - - /// The XML namespace for fields of the ResourceEvent type. - public const string TypeResourceevent = "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"; - - /// The XML namespace for fields of the ResourceRef type. - public const string TypeResourceref = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#"; - - /// The XML namespace for fields of the Version type. - public const string TypeStVersion = "http://ns.adobe.com/xap/1.0/sType/Version#"; - - /// The XML namespace for fields of the JobRef type. - public const string TypeStJob = "http://ns.adobe.com/xap/1.0/sType/Job#"; - - public const string TypeManifestitem = "http://ns.adobe.com/xap/1.0/sType/ManifestItem#"; - - /// The canonical true string value for Booleans in serialized XMP. - /// - /// The canonical true string value for Booleans in serialized XMP. Code that converts from the - /// string to a bool should be case insensitive, and even allow "1". - /// - public const string Truestr = "True"; - - /// The canonical false string value for Booleans in serialized XMP. - /// - /// The canonical false string value for Booleans in serialized XMP. Code that converts from the - /// string to a bool should be case insensitive, and even allow "0". - /// - public const string Falsestr = "False"; - - /// Index that has the meaning to be always the last item in an array. - public const int ArrayLastItem = -1; - - /// Node name of an array item. - public const string ArrayItemName = "[]"; - - /// The x-default string for localized properties - public const string XDefault = "x-default"; - - /// xml:lang qualfifier - public const string XmlLang = "xml:lang"; - - /// rdf:type qualfifier - public const string RdfType = "rdf:type"; - - /// Processing Instruction (PI) for xmp packet - public const string XmpPi = "xpacket"; - - /// XMP meta tag version new - public const string TagXmpmeta = "xmpmeta"; - - /// XMP meta tag version old - public const string TagXapmeta = "xapmeta"; - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +namespace Com.Adobe.Xmp +{ + /// Common constants for the XMP Toolkit. + /// 20.01.2006 + public interface XMPConst + { + // --------------------------------------------------------------------------------------------- + // Standard namespace URI constants + // Standard namespaces + // Adobe standard namespaces + // XMP namespaces that are Adobe private + // XML namespace constants for qualifiers and structured property fields. + // --------------------------------------------------------------------------------------------- + // Basic types and constants + } + + public static class XMPConstConstants + { + /// The XML namespace for XML. + public const string NsXml = "http://www.w3.org/XML/1998/namespace"; + + /// The XML namespace for RDF. + public const string NsRdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + + /// The XML namespace for the Dublin Core schema. + public const string NsDc = "http://purl.org/dc/elements/1.1/"; + + /// The XML namespace for the IPTC Core schema. + public const string NsIptccore = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"; + + /// The XML namespace for the IPTC Extension schema. + public const string NsIptcext = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/"; + + /// The XML namespace for the DICOM medical schema. + public const string NsDicom = "http://ns.adobe.com/DICOM/"; + + /// The XML namespace for the PLUS (Picture Licensing Universal System, http://www.useplus.org) + public const string NsPlus = "http://ns.useplus.org/ldf/xmp/1.0/"; + + /// The XML namespace Adobe XMP Metadata. + public const string NsX = "adobe:ns:meta/"; + + public const string NsIx = "http://ns.adobe.com/iX/1.0/"; + + /// The XML namespace for the XMP "basic" schema. + public const string NsXmp = "http://ns.adobe.com/xap/1.0/"; + + /// The XML namespace for the XMP copyright schema. + public const string NsXmpRights = "http://ns.adobe.com/xap/1.0/rights/"; + + /// The XML namespace for the XMP digital asset management schema. + public const string NsXmpMm = "http://ns.adobe.com/xap/1.0/mm/"; + + /// The XML namespace for the job management schema. + public const string NsXmpBj = "http://ns.adobe.com/xap/1.0/bj/"; + + /// The XML namespace for the job management schema. + public const string NsXmpNote = "http://ns.adobe.com/xmp/note/"; + + /// The XML namespace for the PDF schema. + public const string NsPdf = "http://ns.adobe.com/pdf/1.3/"; + + /// The XML namespace for the PDF schema. + public const string NsPdfx = "http://ns.adobe.com/pdfx/1.3/"; + + public const string NsPdfxId = "http://www.npes.org/pdfx/ns/id/"; + + public const string NsPdfaSchema = "http://www.aiim.org/pdfa/ns/schema#"; + + public const string NsPdfaProperty = "http://www.aiim.org/pdfa/ns/property#"; + + public const string NsPdfaType = "http://www.aiim.org/pdfa/ns/type#"; + + public const string NsPdfaField = "http://www.aiim.org/pdfa/ns/field#"; + + public const string NsPdfaId = "http://www.aiim.org/pdfa/ns/id/"; + + public const string NsPdfaExtension = "http://www.aiim.org/pdfa/ns/extension/"; + + /// The XML namespace for the Photoshop custom schema. + public const string NsPhotoshop = "http://ns.adobe.com/photoshop/1.0/"; + + /// The XML namespace for the Photoshop Album schema. + public const string NsPsalbum = "http://ns.adobe.com/album/1.0/"; + + /// The XML namespace for Adobe's EXIF schema. + public const string NsExif = "http://ns.adobe.com/exif/1.0/"; + + /// NS for the CIPA XMP for Exif document v1.1 + public const string NsExifx = "http://cipa.jp/exif/1.0/"; + + public const string NsExifAux = "http://ns.adobe.com/exif/1.0/aux/"; + + /// The XML namespace for Adobe's TIFF schema. + public const string NsTiff = "http://ns.adobe.com/tiff/1.0/"; + + public const string NsPng = "http://ns.adobe.com/png/1.0/"; + + public const string NsJpeg = "http://ns.adobe.com/jpeg/1.0/"; + + public const string NsJp2k = "http://ns.adobe.com/jp2k/1.0/"; + + public const string NsCameraraw = "http://ns.adobe.com/camera-raw-settings/1.0/"; + + public const string NsAdobestockphoto = "http://ns.adobe.com/StockPhoto/1.0/"; + + public const string NsCreatorAtom = "http://ns.adobe.com/creatorAtom/1.0/"; + + public const string NsAsf = "http://ns.adobe.com/asf/1.0/"; + + public const string NsWav = "http://ns.adobe.com/xmp/wav/1.0/"; + + /// BExt Schema + public const string NsBwf = "http://ns.adobe.com/bwf/bext/1.0/"; + + /// RIFF Info Schema + public const string NsRiffinfo = "http://ns.adobe.com/riff/info/"; + + public const string NsScript = "http://ns.adobe.com/xmp/1.0/Script/"; + + /// Transform XMP + public const string NsTxmp = "http://ns.adobe.com/TransformXMP/"; + + /// Adobe Flash SWF + public const string NsSwf = "http://ns.adobe.com/swf/1.0/"; + + public const string NsDm = "http://ns.adobe.com/xmp/1.0/DynamicMedia/"; + + public const string NsTransient = "http://ns.adobe.com/xmp/transient/1.0/"; + + /// legacy Dublin Core NS, will be converted to NS_DC + public const string NsDcDeprecated = "http://purl.org/dc/1.1/"; + + /// The XML namespace for qualifiers of the xmp:Identifier property. + public const string TypeIdentifierqual = "http://ns.adobe.com/xmp/Identifier/qual/1.0/"; + + /// The XML namespace for fields of the Dimensions type. + public const string TypeDimensions = "http://ns.adobe.com/xap/1.0/sType/Dimensions#"; + + public const string TypeText = "http://ns.adobe.com/xap/1.0/t/"; + + public const string TypePagedfile = "http://ns.adobe.com/xap/1.0/t/pg/"; + + public const string TypeGraphics = "http://ns.adobe.com/xap/1.0/g/"; + + /// The XML namespace for fields of a graphical image. + /// The XML namespace for fields of a graphical image. Used for the Thumbnail type. + public const string TypeImage = "http://ns.adobe.com/xap/1.0/g/img/"; + + public const string TypeFont = "http://ns.adobe.com/xap/1.0/sType/Font#"; + + /// The XML namespace for fields of the ResourceEvent type. + public const string TypeResourceevent = "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"; + + /// The XML namespace for fields of the ResourceRef type. + public const string TypeResourceref = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#"; + + /// The XML namespace for fields of the Version type. + public const string TypeStVersion = "http://ns.adobe.com/xap/1.0/sType/Version#"; + + /// The XML namespace for fields of the JobRef type. + public const string TypeStJob = "http://ns.adobe.com/xap/1.0/sType/Job#"; + + public const string TypeManifestitem = "http://ns.adobe.com/xap/1.0/sType/ManifestItem#"; + + /// The canonical true string value for Booleans in serialized XMP. + /// + /// The canonical true string value for Booleans in serialized XMP. Code that converts from the + /// string to a bool should be case insensitive, and even allow "1". + /// + public const string Truestr = "True"; + + /// The canonical false string value for Booleans in serialized XMP. + /// + /// The canonical false string value for Booleans in serialized XMP. Code that converts from the + /// string to a bool should be case insensitive, and even allow "0". + /// + public const string Falsestr = "False"; + + /// Index that has the meaning to be always the last item in an array. + public const int ArrayLastItem = -1; + + /// Node name of an array item. + public const string ArrayItemName = "[]"; + + /// The x-default string for localized properties + public const string XDefault = "x-default"; + + /// xml:lang qualfifier + public const string XmlLang = "xml:lang"; + + /// rdf:type qualfifier + public const string RdfType = "rdf:type"; + + /// Processing Instruction (PI) for xmp packet + public const string XmpPi = "xpacket"; + + /// XMP meta tag version new + public const string TagXmpmeta = "xmpmeta"; + + /// XMP meta tag version old + public const string TagXapmeta = "xapmeta"; + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTime.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTime.cs index fb6ec22b2..19be97fa0 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTime.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTime.cs @@ -1,116 +1,116 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using Sharpen; - -namespace Com.Adobe.Xmp -{ - /// - /// The XMPDateTime-class represents a point in time up to a resolution of nano - /// seconds. - /// - /// - /// The XMPDateTime-class represents a point in time up to a resolution of nano - /// seconds. Dates and time in the serialized XMP are ISO 8601 strings. There are utility functions - /// to convert to the ISO format, a Calendar or get the Timezone. The fields of - /// XMPDateTime are: - /// - /// DateTime values are occasionally used in cases with only a date or only a time component. A date - /// without a time has zeros for all the time fields. A time without a date has zeros for all date - /// fields (year, month, and day). - /// - public interface XMPDateTime : IComparable - { - /// Returns the year, can be negative. - int GetYear(); - - /// Sets the year - void SetYear(int year); - - /// Returns The month in the range 1..12. - int GetMonth(); - - /// Sets the month 1..12 - void SetMonth(int month); - - /// Returns the day of the month in the range 1..31. - int GetDay(); - - /// Sets the day 1..31 - void SetDay(int day); - - /// Returns hour - The hour in the range 0..23. - int GetHour(); - - /// Sets the hour in the range 0..23. - void SetHour(int hour); - - /// Returns the minute in the range 0..59. - int GetMinute(); - - /// Sets the minute in the range 0..59. - void SetMinute(int minute); - - /// Returns the second in the range 0..59. - int GetSecond(); - - /// Sets the second in the range 0..59. - void SetSecond(int second); - - /// - /// Returns milli-, micro- and nano seconds. - /// Nanoseconds within a second, often left as zero? - /// - int GetNanoSecond(); - - /// - /// Sets the milli-, micro- and nano seconds. - /// Granularity goes down to milli seconds. - /// - void SetNanoSecond(int nanoSecond); - - /// Returns the time zone. - TimeZoneInfo GetTimeZone(); - - /// a time zone to set - void SetTimeZone(TimeZoneInfo tz); - - /// This flag is set either by parsing or by setting year, month or day. - /// Returns true if the XMPDateTime object has a date portion. - bool HasDate(); - - /// This flag is set either by parsing or by setting hours, minutes, seconds or milliseconds. - /// Returns true if the XMPDateTime object has a time portion. - bool HasTime(); - - /// This flag is set either by parsing or by setting hours, minutes, seconds or milliseconds. - /// Returns true if the XMPDateTime object has a defined timezone. - bool HasTimeZone(); - - /// - /// Returns a Calendar (only with milli second precision).
- /// Note: the dates before Oct 15th 1585 (which normally fall into validity of - /// the Julian calendar) are also rendered internally as Gregorian dates. - ///
- Calendar GetCalendar(); - - /// Returns the ISO 8601 string representation of the date and time. - string GetISO8601String(); - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using Sharpen; + +namespace Com.Adobe.Xmp +{ + /// + /// The XMPDateTime-class represents a point in time up to a resolution of nano + /// seconds. + /// + /// + /// The XMPDateTime-class represents a point in time up to a resolution of nano + /// seconds. Dates and time in the serialized XMP are ISO 8601 strings. There are utility functions + /// to convert to the ISO format, a Calendar or get the Timezone. The fields of + /// XMPDateTime are: + /// + /// DateTime values are occasionally used in cases with only a date or only a time component. A date + /// without a time has zeros for all the time fields. A time without a date has zeros for all date + /// fields (year, month, and day). + /// + public interface XMPDateTime : IComparable + { + /// Returns the year, can be negative. + int GetYear(); + + /// Sets the year + void SetYear(int year); + + /// Returns The month in the range 1..12. + int GetMonth(); + + /// Sets the month 1..12 + void SetMonth(int month); + + /// Returns the day of the month in the range 1..31. + int GetDay(); + + /// Sets the day 1..31 + void SetDay(int day); + + /// Returns hour - The hour in the range 0..23. + int GetHour(); + + /// Sets the hour in the range 0..23. + void SetHour(int hour); + + /// Returns the minute in the range 0..59. + int GetMinute(); + + /// Sets the minute in the range 0..59. + void SetMinute(int minute); + + /// Returns the second in the range 0..59. + int GetSecond(); + + /// Sets the second in the range 0..59. + void SetSecond(int second); + + /// + /// Returns milli-, micro- and nano seconds. + /// Nanoseconds within a second, often left as zero? + /// + int GetNanoSecond(); + + /// + /// Sets the milli-, micro- and nano seconds. + /// Granularity goes down to milli seconds. + /// + void SetNanoSecond(int nanoSecond); + + /// Returns the time zone. + TimeZoneInfo GetTimeZone(); + + /// a time zone to set + void SetTimeZone(TimeZoneInfo tz); + + /// This flag is set either by parsing or by setting year, month or day. + /// Returns true if the XMPDateTime object has a date portion. + bool HasDate(); + + /// This flag is set either by parsing or by setting hours, minutes, seconds or milliseconds. + /// Returns true if the XMPDateTime object has a time portion. + bool HasTime(); + + /// This flag is set either by parsing or by setting hours, minutes, seconds or milliseconds. + /// Returns true if the XMPDateTime object has a defined timezone. + bool HasTimeZone(); + + /// + /// Returns a Calendar (only with milli second precision).
+ /// Note: the dates before Oct 15th 1585 (which normally fall into validity of + /// the Julian calendar) are also rendered internally as Gregorian dates. + ///
+ Calendar GetCalendar(); + + /// Returns the ISO 8601 string representation of the date and time. + string GetISO8601String(); + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTimeFactory.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTimeFactory.cs index e7df18fc2..136c90021 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTimeFactory.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPDateTimeFactory.cs @@ -1,161 +1,161 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using Com.Adobe.Xmp.Impl; -using Sharpen; - -namespace Com.Adobe.Xmp -{ - /// - /// A factory to create XMPDateTime-instances from a Calendar or an - /// ISO 8601 string or for the current time. - /// - /// 16.02.2006 - public sealed class XMPDateTimeFactory - { - /// The UTC TimeZone - private static readonly TimeZoneInfo Utc = Extensions.GetTimeZone("UTC"); - - /// Private constructor - private XMPDateTimeFactory() - { - } - - // EMPTY - /// Creates an XMPDateTime from a Calendar-object. - /// a Calendar-object. - /// An XMPDateTime-object. - public static XMPDateTime CreateFromCalendar(Calendar calendar) - { - return new XMPDateTimeImpl(calendar); - } - - /// Creates an empty XMPDateTime-object. - /// Returns an XMPDateTime-object. - public static XMPDateTime Create() - { - return new XMPDateTimeImpl(); - } - - /// Creates an XMPDateTime-object from initial values. - /// years - /// - /// months from 1 to 12
- /// Note: Remember that the month in - /// - /// is defined from 0 to 11. - /// - /// days - /// Returns an XMPDateTime-object. - public static XMPDateTime Create(int year, int month, int day) - { - XMPDateTime dt = new XMPDateTimeImpl(); - dt.SetYear(year); - dt.SetMonth(month); - dt.SetDay(day); - return dt; - } - - /// Creates an XMPDateTime-object from initial values. - /// years - /// - /// months from 1 to 12
- /// Note: Remember that the month in - /// - /// is defined from 0 to 11. - /// - /// days - /// hours - /// minutes - /// seconds - /// nanoseconds - /// Returns an XMPDateTime-object. - public static XMPDateTime Create(int year, int month, int day, int hour, int minute, int second, int nanoSecond) - { - XMPDateTime dt = new XMPDateTimeImpl(); - dt.SetYear(year); - dt.SetMonth(month); - dt.SetDay(day); - dt.SetHour(hour); - dt.SetMinute(minute); - dt.SetSecond(second); - dt.SetNanoSecond(nanoSecond); - return dt; - } - - /// Creates an XMPDateTime from an ISO 8601 string. - /// The ISO 8601 string representation of the date/time. - /// An XMPDateTime-object. - /// When the ISO 8601 string is non-conform - /// - public static XMPDateTime CreateFromISO8601(string strValue) - { - return new XMPDateTimeImpl(strValue); - } - - /// Obtain the current date and time. - /// - /// Returns The returned time is UTC, properly adjusted for the local time zone. The - /// resolution of the time is not guaranteed to be finer than seconds. - /// - public static XMPDateTime GetCurrentDateTime() - { - return new XMPDateTimeImpl(new GregorianCalendar()); - } - - /// - /// Sets the local time zone without touching any other Any existing time zone value is replaced, - /// the other date/time fields are not adjusted in any way. - /// - /// the XMPDateTime variable containing the value to be modified. - /// Returns an updated XMPDateTime-object. - public static XMPDateTime SetLocalTimeZone(XMPDateTime dateTime) - { - Calendar cal = dateTime.GetCalendar(); - cal.SetTimeZone(TimeZoneInfo.Local); - return new XMPDateTimeImpl(cal); - } - - /// Make sure a time is UTC. - /// - /// Make sure a time is UTC. If the time zone is not UTC, the time is - /// adjusted and the time zone set to be UTC. - /// - /// - /// the XMPDateTime variable containing the time to - /// be modified. - /// - /// Returns an updated XMPDateTime-object. - public static XMPDateTime ConvertToUTCTime(XMPDateTime dateTime) - { - long timeInMillis = dateTime.GetCalendar().GetTimeInMillis(); - GregorianCalendar cal = new GregorianCalendar(Utc); - cal.SetGregorianChange(Extensions.CreateDate(long.MinValue)); - cal.SetTimeInMillis(timeInMillis); - return new XMPDateTimeImpl(cal); - } - - /// Make sure a time is local. - /// - /// Make sure a time is local. If the time zone is not the local zone, the time is adjusted and - /// the time zone set to be local. - /// - /// the XMPDateTime variable containing the time to be modified. - /// Returns an updated XMPDateTime-object. - public static XMPDateTime ConvertToLocalTime(XMPDateTime dateTime) - { - long timeInMillis = dateTime.GetCalendar().GetTimeInMillis(); - // has automatically local timezone - GregorianCalendar cal = new GregorianCalendar(); - cal.SetTimeInMillis(timeInMillis); - return new XMPDateTimeImpl(cal); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using Com.Adobe.Xmp.Impl; +using Sharpen; + +namespace Com.Adobe.Xmp +{ + /// + /// A factory to create XMPDateTime-instances from a Calendar or an + /// ISO 8601 string or for the current time. + /// + /// 16.02.2006 + public sealed class XMPDateTimeFactory + { + /// The UTC TimeZone + private static readonly TimeZoneInfo Utc = Extensions.GetTimeZone("UTC"); + + /// Private constructor + private XMPDateTimeFactory() + { + } + + // EMPTY + /// Creates an XMPDateTime from a Calendar-object. + /// a Calendar-object. + /// An XMPDateTime-object. + public static XMPDateTime CreateFromCalendar(Calendar calendar) + { + return new XMPDateTimeImpl(calendar); + } + + /// Creates an empty XMPDateTime-object. + /// Returns an XMPDateTime-object. + public static XMPDateTime Create() + { + return new XMPDateTimeImpl(); + } + + /// Creates an XMPDateTime-object from initial values. + /// years + /// + /// months from 1 to 12
+ /// Note: Remember that the month in + /// + /// is defined from 0 to 11. + /// + /// days + /// Returns an XMPDateTime-object. + public static XMPDateTime Create(int year, int month, int day) + { + XMPDateTime dt = new XMPDateTimeImpl(); + dt.SetYear(year); + dt.SetMonth(month); + dt.SetDay(day); + return dt; + } + + /// Creates an XMPDateTime-object from initial values. + /// years + /// + /// months from 1 to 12
+ /// Note: Remember that the month in + /// + /// is defined from 0 to 11. + /// + /// days + /// hours + /// minutes + /// seconds + /// nanoseconds + /// Returns an XMPDateTime-object. + public static XMPDateTime Create(int year, int month, int day, int hour, int minute, int second, int nanoSecond) + { + XMPDateTime dt = new XMPDateTimeImpl(); + dt.SetYear(year); + dt.SetMonth(month); + dt.SetDay(day); + dt.SetHour(hour); + dt.SetMinute(minute); + dt.SetSecond(second); + dt.SetNanoSecond(nanoSecond); + return dt; + } + + /// Creates an XMPDateTime from an ISO 8601 string. + /// The ISO 8601 string representation of the date/time. + /// An XMPDateTime-object. + /// When the ISO 8601 string is non-conform + /// + public static XMPDateTime CreateFromISO8601(string strValue) + { + return new XMPDateTimeImpl(strValue); + } + + /// Obtain the current date and time. + /// + /// Returns The returned time is UTC, properly adjusted for the local time zone. The + /// resolution of the time is not guaranteed to be finer than seconds. + /// + public static XMPDateTime GetCurrentDateTime() + { + return new XMPDateTimeImpl(new GregorianCalendar()); + } + + /// + /// Sets the local time zone without touching any other Any existing time zone value is replaced, + /// the other date/time fields are not adjusted in any way. + /// + /// the XMPDateTime variable containing the value to be modified. + /// Returns an updated XMPDateTime-object. + public static XMPDateTime SetLocalTimeZone(XMPDateTime dateTime) + { + Calendar cal = dateTime.GetCalendar(); + cal.SetTimeZone(TimeZoneInfo.Local); + return new XMPDateTimeImpl(cal); + } + + /// Make sure a time is UTC. + /// + /// Make sure a time is UTC. If the time zone is not UTC, the time is + /// adjusted and the time zone set to be UTC. + /// + /// + /// the XMPDateTime variable containing the time to + /// be modified. + /// + /// Returns an updated XMPDateTime-object. + public static XMPDateTime ConvertToUTCTime(XMPDateTime dateTime) + { + long timeInMillis = dateTime.GetCalendar().GetTimeInMillis(); + GregorianCalendar cal = new GregorianCalendar(Utc); + cal.SetGregorianChange(Extensions.CreateDate(long.MinValue)); + cal.SetTimeInMillis(timeInMillis); + return new XMPDateTimeImpl(cal); + } + + /// Make sure a time is local. + /// + /// Make sure a time is local. If the time zone is not the local zone, the time is adjusted and + /// the time zone set to be local. + /// + /// the XMPDateTime variable containing the time to be modified. + /// Returns an updated XMPDateTime-object. + public static XMPDateTime ConvertToLocalTime(XMPDateTime dateTime) + { + long timeInMillis = dateTime.GetCalendar().GetTimeInMillis(); + // has automatically local timezone + GregorianCalendar cal = new GregorianCalendar(); + cal.SetTimeInMillis(timeInMillis); + return new XMPDateTimeImpl(cal); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPError.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPError.cs index a63017c24..895f3f30d 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPError.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPError.cs @@ -1,46 +1,46 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -namespace Com.Adobe.Xmp -{ - /// 21.09.2006 - public interface XMPError - { - } - - public static class XMPErrorConstants - { - public const int Unknown = 0; - - public const int Badparam = 4; - - public const int Badvalue = 5; - - public const int Internalfailure = 9; - - public const int Badschema = 101; - - public const int Badxpath = 102; - - public const int Badoptions = 103; - - public const int Badindex = 104; - - public const int Badserialize = 107; - - public const int Badxml = 201; - - public const int Badrdf = 202; - - public const int Badxmp = 203; - - /// Note: This is an error code introduced by Java. - public const int Badstream = 204; - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +namespace Com.Adobe.Xmp +{ + /// 21.09.2006 + public interface XMPError + { + } + + public static class XMPErrorConstants + { + public const int Unknown = 0; + + public const int Badparam = 4; + + public const int Badvalue = 5; + + public const int Internalfailure = 9; + + public const int Badschema = 101; + + public const int Badxpath = 102; + + public const int Badoptions = 103; + + public const int Badindex = 104; + + public const int Badserialize = 107; + + public const int Badxml = 201; + + public const int Badrdf = 202; + + public const int Badxmp = 203; + + /// Note: This is an error code introduced by Java. + public const int Badstream = 204; + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPException.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPException.cs index 147f2f598..e4a54a276 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPException.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPException.cs @@ -1,47 +1,47 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; - -namespace Com.Adobe.Xmp -{ - /// This exception wraps all errors that occur in the XMP Toolkit. - /// 16.02.2006 - [Serializable] - public class XMPException : Exception - { - /// the errorCode of the XMP toolkit - private int errorCode; - - /// Constructs an exception with a message and an error code. - /// the message - /// the error code - public XMPException(string message, int errorCode) - : base(message) - { - this.errorCode = errorCode; - } - - /// Constructs an exception with a message, an error code and a Throwable - /// the error message. - /// the error code - /// the exception source - public XMPException(string message, int errorCode, Exception t) - : base(message, t) - { - this.errorCode = errorCode; - } - - /// Returns the errorCode. - public virtual int GetErrorCode() - { - return errorCode; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; + +namespace Com.Adobe.Xmp +{ + /// This exception wraps all errors that occur in the XMP Toolkit. + /// 16.02.2006 + [Serializable] + public class XMPException : Exception + { + /// the errorCode of the XMP toolkit + private int errorCode; + + /// Constructs an exception with a message and an error code. + /// the message + /// the error code + public XMPException(string message, int errorCode) + : base(message) + { + this.errorCode = errorCode; + } + + /// Constructs an exception with a message, an error code and a Throwable + /// the error message. + /// the error code + /// the exception source + public XMPException(string message, int errorCode, Exception t) + : base(message, t) + { + this.errorCode = errorCode; + } + + /// Returns the errorCode. + public virtual int GetErrorCode() + { + return errorCode; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPIterator.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPIterator.cs index 5e9bf576d..e2b86fdad 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPIterator.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPIterator.cs @@ -1,81 +1,81 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using Sharpen; - -namespace Com.Adobe.Xmp -{ - /// Interface for the XMPMeta iteration services. - /// - /// Interface for the XMPMeta iteration services. - /// XMPIterator provides a uniform means to iterate over the - /// schema and properties within an XMP object. - ///

- /// The iteration over the schema and properties within an XMP object is very - /// complex. It is helpful to have a thorough understanding of the XMP data tree. - /// One way to learn this is to create some complex XMP and examine the output of - /// XMPMeta#toString. This is also described in the XMP - /// Specification, in the XMP Data Model chapter. - ///

- /// The top of the XMP data tree is a single root node. This does not explicitly - /// appear in the dump and is never visited by an iterator (that is, it is never - /// returned from XMPIterator#next()). Beneath the root are - /// schema nodes. These are just collectors for top level properties in the same - /// namespace. They are created and destroyed implicitly. Beneath the schema - /// nodes are the property nodes. The nodes below a property node depend on its - /// type (simple, struct, or array) and whether it has qualifiers. - ///

- /// An XMPIterator is created by XMPMeta#interator() constructor - /// defines a starting point for the iteration and options that control how it - /// proceeds. By default the iteration starts at the root and visits all nodes - /// beneath it in a depth first manner. The root node is not visited, the first - /// visited node is a schema node. You can provide a schema name or property path - /// to select a different starting node. By default this visits the named root - /// node first then all nodes beneath it in a depth first manner. - ///

- /// The XMPIterator#next() method delivers the schema URI, path, - /// and option flags for the node being visited. If the node is simple it also - /// delivers the value. Qualifiers for this node are visited next. The fields of - /// a struct or items of an array are visited after the qualifiers of the parent. - ///

- /// The options to control the iteration are: - ///

    - ///
  • JUST_CHILDREN - Visit just the immediate children of the root. Skip - /// the root itself and all nodes below the immediate children. This omits the - /// qualifiers of the immediate children, the qualifier nodes being below what - /// they qualify, default is to visit the complete subtree. - ///
  • JUST_LEAFNODES - Visit just the leaf property nodes and their - /// qualifiers. - ///
  • JUST_LEAFNAME - Return just the leaf component of the node names. - /// The default is to return the full xmp path. - ///
  • OMIT_QUALIFIERS - Do not visit the qualifiers. - ///
  • INCLUDE_ALIASES - Adds known alias properties to the properties in the iteration. - /// Note: Not supported in Java XMPCore! - ///
- ///

- /// next() returns XMPPropertyInfo-objects and throws - /// a NoSuchElementException if there are no more properties to - /// return. - /// - /// 25.01.2006 - public interface XMPIterator : Iterator - { - ///

- /// Skip the subtree below the current node when next() is - /// called. - /// - void SkipSubtree(); - - /// - /// Skip the subtree below and remaining siblings of the current node when - /// next() is called. - /// - void SkipSiblings(); - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using Sharpen; + +namespace Com.Adobe.Xmp +{ + /// Interface for the XMPMeta iteration services. + /// + /// Interface for the XMPMeta iteration services. + /// XMPIterator provides a uniform means to iterate over the + /// schema and properties within an XMP object. + ///

+ /// The iteration over the schema and properties within an XMP object is very + /// complex. It is helpful to have a thorough understanding of the XMP data tree. + /// One way to learn this is to create some complex XMP and examine the output of + /// XMPMeta#toString. This is also described in the XMP + /// Specification, in the XMP Data Model chapter. + ///

+ /// The top of the XMP data tree is a single root node. This does not explicitly + /// appear in the dump and is never visited by an iterator (that is, it is never + /// returned from XMPIterator#next()). Beneath the root are + /// schema nodes. These are just collectors for top level properties in the same + /// namespace. They are created and destroyed implicitly. Beneath the schema + /// nodes are the property nodes. The nodes below a property node depend on its + /// type (simple, struct, or array) and whether it has qualifiers. + ///

+ /// An XMPIterator is created by XMPMeta#interator() constructor + /// defines a starting point for the iteration and options that control how it + /// proceeds. By default the iteration starts at the root and visits all nodes + /// beneath it in a depth first manner. The root node is not visited, the first + /// visited node is a schema node. You can provide a schema name or property path + /// to select a different starting node. By default this visits the named root + /// node first then all nodes beneath it in a depth first manner. + ///

+ /// The XMPIterator#next() method delivers the schema URI, path, + /// and option flags for the node being visited. If the node is simple it also + /// delivers the value. Qualifiers for this node are visited next. The fields of + /// a struct or items of an array are visited after the qualifiers of the parent. + ///

+ /// The options to control the iteration are: + ///

    + ///
  • JUST_CHILDREN - Visit just the immediate children of the root. Skip + /// the root itself and all nodes below the immediate children. This omits the + /// qualifiers of the immediate children, the qualifier nodes being below what + /// they qualify, default is to visit the complete subtree. + ///
  • JUST_LEAFNODES - Visit just the leaf property nodes and their + /// qualifiers. + ///
  • JUST_LEAFNAME - Return just the leaf component of the node names. + /// The default is to return the full xmp path. + ///
  • OMIT_QUALIFIERS - Do not visit the qualifiers. + ///
  • INCLUDE_ALIASES - Adds known alias properties to the properties in the iteration. + /// Note: Not supported in Java XMPCore! + ///
+ ///

+ /// next() returns XMPPropertyInfo-objects and throws + /// a NoSuchElementException if there are no more properties to + /// return. + /// + /// 25.01.2006 + public interface XMPIterator : Iterator + { + ///

+ /// Skip the subtree below the current node when next() is + /// called. + /// + void SkipSubtree(); + + /// + /// Skip the subtree below and remaining siblings of the current node when + /// next() is called. + /// + void SkipSiblings(); + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPMeta.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPMeta.cs index bf4ff3731..3ca68a469 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPMeta.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPMeta.cs @@ -1,1187 +1,1187 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using Com.Adobe.Xmp.Options; -using Com.Adobe.Xmp.Properties; -using Sharpen; - -namespace Com.Adobe.Xmp -{ - /// This class represents the set of XMP metadata as a DOM representation. - /// - /// This class represents the set of XMP metadata as a DOM representation. It has methods to read and - /// modify all kinds of properties, create an iterator over all properties and serialize the metadata - /// to a String, byte-array or OutputStream. - /// - /// 20.01.2006 - public interface XMPMeta : ICloneable - { - // --------------------------------------------------------------------------------------------- - // Basic property manipulation functions - /// - /// The property value getter-methods all take a property specification: the first two parameters - /// are always the top level namespace URI (the "schema" namespace) and the basic name - /// of the property being referenced. - /// - /// - /// The property value getter-methods all take a property specification: the first two parameters - /// are always the top level namespace URI (the "schema" namespace) and the basic name - /// of the property being referenced. See the introductory discussion of path expression usage - /// for more information. - ///

- /// All of the functions return an object inherited from PropertyBase or - /// null if the property does not exists. The result object contains the value of - /// the property and option flags describing the property. Arrays and the non-leaf levels of - /// nodes do not have values. - ///

- /// See - /// - /// for detailed information about the options. - ///

- /// This is the simplest property getter, mainly for top level simple properties or after using - /// the path composition functions in XMPPathFactory. - /// - /// - /// The namespace URI for the property. May be null or the empty - /// string if the first component of the propName path contains a namespace prefix. The - /// URI must be for a registered namespace. - /// - /// - /// The name of the property. May be a general path expression, must not be - /// null or the empty string. Using a namespace prefix on the first - /// component is optional. If present without a schemaNS value then the prefix specifies - /// the namespace. The prefix must be for a registered namespace. If both a schemaNS URI - /// and propName prefix are present, they must be corresponding parts of a registered - /// namespace. - /// - /// - /// Returns a XMPProperty containing the value and the options or - /// null if the property does not exist. - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPProperty GetProperty(string schemaNS, string propName); - - ///

Provides access to items within an array. - /// - /// Provides access to items within an array. The index is passed as an integer, you need not - /// worry about the path string syntax for array items, convert a loop index to a string, etc. - /// - /// The namespace URI for the array. Has the same usage as in getProperty. - /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// - /// The index of the desired item. Arrays in XMP are indexed from 1. The - /// constant - /// - /// always refers to the last existing array - /// item. - /// - /// - /// Returns a XMPProperty containing the value and the options or - /// null if the property does not exist. - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPProperty GetArrayItem(string schemaNS, string arrayName, int itemIndex); - - /// Returns the number of items in the array. - /// The namespace URI for the array. Has the same usage as in getProperty. - /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// Returns the number of items in the array. - /// Wraps all errors and exceptions that may occur. - /// - int CountArrayItems(string schemaNS, string arrayName); - - /// Provides access to fields within a nested structure. - /// - /// Provides access to fields within a nested structure. The namespace for the field is passed as - /// a URI, you need not worry about the path string syntax. - ///

- /// The names of fields should be XML qualified names, that is within an XML namespace. The path - /// syntax for a qualified name uses the namespace prefix. This is unreliable since the prefix is - /// never guaranteed. The URI is the formal name, the prefix is just a local shorthand in a given - /// sequence of XML text. - /// - /// The namespace URI for the struct. Has the same usage as in getProperty. - /// - /// The name of the struct. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// - /// The namespace URI for the field. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the field. Must be a single XML name, must not be - /// null or the empty string. Has the same namespace prefix usage as the - /// structName parameter. - /// - /// - /// Returns a XMPProperty containing the value and the options or - /// null if the property does not exist. Arrays and non-leaf levels of - /// structs do not have values. - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPProperty GetStructField(string schemaNS, string structName, string fieldNS, string fieldName); - - ///

Provides access to a qualifier attached to a property. - /// - /// Provides access to a qualifier attached to a property. The namespace for the qualifier is - /// passed as a URI, you need not worry about the path string syntax. In many regards qualifiers - /// are like struct fields. See the introductory discussion of qualified properties for more - /// information. - ///

- /// The names of qualifiers should be XML qualified names, that is within an XML namespace. The - /// path syntax for a qualified name uses the namespace prefix. This is unreliable since the - /// prefix is never guaranteed. The URI is the formal name, the prefix is just a local shorthand - /// in a given sequence of XML text. - ///

- /// Note: Qualifiers are only supported for simple leaf properties at this time. - /// - /// The namespace URI for the struct. Has the same usage as in getProperty. - /// - /// The name of the property to which the qualifier is attached. May be a general - /// path expression, must not be null or the empty string. Has the same - /// namespace prefix usage as in getProperty(). - /// - /// - /// The namespace URI for the qualifier. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the qualifier. Must be a single XML name, must not be - /// null or the empty string. Has the same namespace prefix usage as the - /// propName parameter. - /// - /// - /// Returns a XMPProperty containing the value and the options of the - /// qualifier or null if the property does not exist. The name of the - /// qualifier must be a single XML name, must not be null or the empty - /// string. Has the same namespace prefix usage as the propName parameter. - ///

- /// The value of the qualifier is only set if it has one (Arrays and non-leaf levels of - /// structs do not have values). - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPProperty GetQualifier(string schemaNS, string propName, string qualNS, string qualName); - - // --------------------------------------------------------------------------------------------- - // Functions for setting property values - ///

- /// The property value setters all take a property specification, their - /// differences are in the form of this. - /// - /// - /// The property value setters all take a property specification, their - /// differences are in the form of this. The first two parameters are always the top level - /// namespace URI (the schema namespace) and the basic name of the property being - /// referenced. See the introductory discussion of path expression usage for more information. - ///

- /// All of the functions take a string value for the property and option flags describing the - /// property. The value must be Unicode in UTF-8 encoding. Arrays and non-leaf levels of structs - /// do not have values. Empty arrays and structs may be created using appropriate option flags. - /// All levels of structs that is assigned implicitly are created if necessary. appendArayItem - /// implicitly creates the named array if necessary. - ///

- /// See - /// - /// for detailed information about the options. - ///

- /// This is the simplest property setter, mainly for top level simple properties or after using - /// the path composition functions in - /// - /// . - /// - /// The namespace URI for the property. Has the same usage as in getProperty. - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// the value for the property (only leaf properties have a value). - /// Arrays and non-leaf levels of structs do not have values. - /// Must be null if the value is not relevant.
- /// The value is automatically detected: Boolean, Integer, Long, Double, XMPDateTime and - /// byte[] are handled, on all other toString() is called. - /// - /// Option flags describing the property. See the earlier description. - /// Wraps all errors and exceptions that may occur. - /// - void SetProperty(string schemaNS, string propName, object propValue, PropertyOptions options); - - /// - /// The namespace URI - /// The name of the property - /// the value for the property - /// Wraps all errors and exceptions - /// - void SetProperty(string schemaNS, string propName, object propValue); - - ///

Replaces an item within an array. - /// - /// Replaces an item within an array. The index is passed as an integer, you need not worry about - /// the path string syntax for array items, convert a loop index to a string, etc. The array - /// passed must already exist. In normal usage the selected array item is modified. A new item is - /// automatically appended if the index is the array size plus 1. - /// - /// The namespace URI for the array. Has the same usage as in getProperty. - /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty. - /// - /// - /// The index of the desired item. Arrays in XMP are indexed from 1. To address - /// the last existing item, use - /// - /// to find - /// out the length of the array. - /// - /// - /// the new value of the array item. Has the same usage as propValue in - /// setProperty(). - /// - /// the set options for the item. - /// Wraps all errors and exceptions that may occur. - /// - void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options); - - /// - /// The namespace URI - /// The name of the array - /// The index to insert the new item - /// the new value of the array item - /// Wraps all errors and exceptions - /// - void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue); - - /// Inserts an item into an array previous to the given index. - /// - /// Inserts an item into an array previous to the given index. The index is passed as an integer, - /// you need not worry about the path string syntax for array items, convert a loop index to a - /// string, etc. The array passed must already exist. In normal usage the selected array item is - /// modified. A new item is automatically appended if the index is the array size plus 1. - /// - /// The namespace URI for the array. Has the same usage as in getProperty. - /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty. - /// - /// - /// The index to insert the new item. Arrays in XMP are indexed from 1. Use - /// XMPConst.ARRAY_LAST_ITEM to append items. - /// - /// - /// the new value of the array item. Has the same usage as - /// propValue in setProperty(). - /// - /// the set options that decide about the kind of the node. - /// Wraps all errors and exceptions that may occur. - /// - void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options); - - /// - /// The namespace URI for the array - /// The name of the array - /// The index to insert the new item - /// the value of the array item - /// Wraps all errors and exceptions - /// - void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue); - - /// Simplifies the construction of an array by not requiring that you pre-create an empty array. - /// - /// Simplifies the construction of an array by not requiring that you pre-create an empty array. - /// The array that is assigned is created automatically if it does not yet exist. Each call to - /// appendArrayItem() appends an item to the array. The corresponding parameters have the same - /// use as setArrayItem(). The arrayOptions parameter is used to specify what kind of array. If - /// the array exists, it must have the specified form. - /// - /// The namespace URI for the array. Has the same usage as in getProperty. - /// - /// The name of the array. May be a general path expression, must not be null or - /// the empty string. Has the same namespace prefix usage as propPath in getProperty. - /// - /// - /// Option flags describing the array form. The only valid options are - ///
    - ///
  • - /// - /// , - ///
  • - /// - /// , - ///
  • - /// - /// or - ///
  • - /// - /// . - ///
- /// Note: the array options only need to be provided if the array is not - /// already existing, otherwise you can set them to null or use - /// - /// . - /// - /// the value of the array item. Has the same usage as propValue in getProperty. - /// - /// Option flags describing the item to append ( - /// - /// ) - /// - /// Wraps all errors and exceptions that may occur. - /// - void AppendArrayItem(string schemaNS, string arrayName, PropertyOptions arrayOptions, string itemValue, PropertyOptions itemOptions); - - /// - /// The namespace URI for the array - /// The name of the array - /// the value of the array item - /// Wraps all errors and exceptions - /// - void AppendArrayItem(string schemaNS, string arrayName, string itemValue); - - /// Provides access to fields within a nested structure. - /// - /// Provides access to fields within a nested structure. The namespace for the field is passed as - /// a URI, you need not worry about the path string syntax. The names of fields should be XML - /// qualified names, that is within an XML namespace. The path syntax for a qualified name uses - /// the namespace prefix, which is unreliable because the prefix is never guaranteed. The URI is - /// the formal name, the prefix is just a local shorthand in a given sequence of XML text. - /// - /// The namespace URI for the struct. Has the same usage as in getProperty. - /// - /// The name of the struct. May be a general path expression, must not be null - /// or the empty string. Has the same namespace prefix usage as propName in getProperty. - /// - /// - /// The namespace URI for the field. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the field. Must be a single XML name, must not be null or the - /// empty string. Has the same namespace prefix usage as the structName parameter. - /// - /// - /// the value of thefield, if the field has a value. - /// Has the same usage as propValue in getProperty. - /// - /// Option flags describing the field. See the earlier description. - /// Wraps all errors and exceptions that may occur. - /// - void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue, PropertyOptions options); - - /// - /// The namespace URI for the struct - /// The name of the struct - /// The namespace URI for the field - /// The name of the field - /// the value of the field - /// Wraps all errors and exceptions - /// - void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue); - - /// Provides access to a qualifier attached to a property. - /// - /// Provides access to a qualifier attached to a property. The namespace for the qualifier is - /// passed as a URI, you need not worry about the path string syntax. In many regards qualifiers - /// are like struct fields. See the introductory discussion of qualified properties for more - /// information. The names of qualifiers should be XML qualified names, that is within an XML - /// namespace. The path syntax for a qualified name uses the namespace prefix, which is - /// unreliable because the prefix is never guaranteed. The URI is the formal name, the prefix is - /// just a local shorthand in a given sequence of XML text. The property the qualifier - /// will be attached has to exist. - /// - /// The namespace URI for the struct. Has the same usage as in getProperty. - /// - /// The name of the property to which the qualifier is attached. Has the same - /// usage as in getProperty. - /// - /// - /// The namespace URI for the qualifier. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the qualifier. Must be a single XML name, must not be - /// null or the empty string. Has the same namespace prefix usage as the - /// propName parameter. - /// - /// - /// A pointer to the null terminated UTF-8 string that is the - /// value of the qualifier, if the qualifier has a value. Has the same usage as propValue - /// in getProperty. - /// - /// Option flags describing the qualifier. See the earlier description. - /// Wraps all errors and exceptions that may occur. - /// - void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue, PropertyOptions options); - - /// - /// The namespace URI for the struct - /// The name of the property to which the qualifier is attached - /// The namespace URI for the qualifier - /// The name of the qualifier - /// the value of the qualifier - /// Wraps all errors and exceptions - /// - void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue); - - // --------------------------------------------------------------------------------------------- - // Functions for deleting and detecting properties. These should be obvious from the - // descriptions of the getters and setters. - /// Deletes the given XMP subtree rooted at the given property. - /// - /// Deletes the given XMP subtree rooted at the given property. It is not an error if the - /// property does not exist. - /// - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// The name of the property. Has the same usage as in getProperty. - void DeleteProperty(string schemaNS, string propName); - - /// Deletes the given XMP subtree rooted at the given array item. - /// - /// Deletes the given XMP subtree rooted at the given array item. It is not an error if the array - /// item does not exist. - /// - /// The namespace URI for the array. Has the same usage as in getProperty. - /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// - /// The index of the desired item. Arrays in XMP are indexed from 1. The - /// constant XMPConst.ARRAY_LAST_ITEM always refers to the last - /// existing array item. - /// - void DeleteArrayItem(string schemaNS, string arrayName, int itemIndex); - - /// Deletes the given XMP subtree rooted at the given struct field. - /// - /// Deletes the given XMP subtree rooted at the given struct field. It is not an error if the - /// field does not exist. - /// - /// - /// The namespace URI for the struct. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the struct. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty. - /// - /// - /// The namespace URI for the field. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the field. Must be a single XML name, must not be - /// null or the empty string. Has the same namespace prefix usage as the - /// structName parameter. - /// - void DeleteStructField(string schemaNS, string structName, string fieldNS, string fieldName); - - /// Deletes the given XMP subtree rooted at the given qualifier. - /// - /// Deletes the given XMP subtree rooted at the given qualifier. It is not an error if the - /// qualifier does not exist. - /// - /// - /// The namespace URI for the struct. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property to which the qualifier is attached. Has the same - /// usage as in getProperty. - /// - /// - /// The namespace URI for the qualifier. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the qualifier. Must be a single XML name, must not be - /// null or the empty string. Has the same namespace prefix usage as the - /// propName parameter. - /// - void DeleteQualifier(string schemaNS, string propName, string qualNS, string qualName); - - /// Returns whether the property exists. - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// Returns true if the property exists. - bool DoesPropertyExist(string schemaNS, string propName); - - /// Tells if the array item exists. - /// - /// The namespace URI for the array. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// - /// The index of the desired item. Arrays in XMP are indexed from 1. The - /// constant XMPConst.ARRAY_LAST_ITEM always refers to the last - /// existing array item. - /// - /// Returns true if the array exists, false otherwise. - bool DoesArrayItemExist(string schemaNS, string arrayName, int itemIndex); - - /// DoesStructFieldExist tells if the struct field exists. - /// - /// The namespace URI for the struct. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the struct. May be a general path expression, must not be - /// null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// - /// The namespace URI for the field. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the field. Must be a single XML name, must not be - /// null or the empty string. Has the same namespace prefix usage as the - /// structName parameter. - /// - /// Returns true if the field exists. - bool DoesStructFieldExist(string schemaNS, string structName, string fieldNS, string fieldName); - - /// DoesQualifierExist tells if the qualifier exists. - /// - /// The namespace URI for the struct. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property to which the qualifier is attached. Has the same - /// usage as in getProperty(). - /// - /// - /// The namespace URI for the qualifier. Has the same URI and prefix usage as the - /// schemaNS parameter. - /// - /// - /// The name of the qualifier. Must be a single XML name, must not be - /// null or the empty string. Has the same namespace prefix usage as the - /// propName parameter. - /// - /// Returns true if the qualifier exists. - bool DoesQualifierExist(string schemaNS, string propName, string qualNS, string qualName); - - // --------------------------------------------------------------------------------------------- - // Specialized Get and Set functions - /// - /// These functions provide convenient support for localized text properties, including a number - /// of special and obscure aspects. - /// - /// - /// These functions provide convenient support for localized text properties, including a number - /// of special and obscure aspects. Localized text properties are stored in alt-text arrays. They - /// allow multiple concurrent localizations of a property value, for example a document title or - /// copyright in several languages. The most important aspect of these functions is that they - /// select an appropriate array item based on one or two RFC 3066 language tags. One of these - /// languages, the "specific" language, is preferred and selected if there is an exact match. For - /// many languages it is also possible to define a "generic" language that may be used if there - /// is no specific language match. The generic language must be a valid RFC 3066 primary subtag, - /// or the empty string. For example, a specific language of "en-US" should be used in the US, - /// and a specific language of "en-UK" should be used in England. It is also appropriate to use - /// "en" as the generic language in each case. If a US document goes to England, the "en-US" - /// title is selected by using the "en" generic language and the "en-UK" specific language. It is - /// considered poor practice, but allowed, to pass a specific language that is just an RFC 3066 - /// primary tag. For example "en" is not a good specific language, it should only be used as a - /// generic language. Passing "i" or "x" as the generic language is also considered poor practice - /// but allowed. Advice from the W3C about the use of RFC 3066 language tags can be found at: - /// http://www.w3.org/International/articles/language-tags/ - ///

- /// Note: RFC 3066 language tags must be treated in a case insensitive manner. The XMP - /// Toolkit does this by normalizing their capitalization: - ///

    - ///
  • The primary subtag is lower case, the suggested practice of ISO 639. - ///
  • All 2 letter secondary subtags are upper case, the suggested practice of ISO 3166. - ///
  • All other subtags are lower case. The XMP specification defines an artificial language, - ///
  • "x-default", that is used to explicitly denote a default item in an alt-text array. - ///
- /// The XMP toolkit normalizes alt-text arrays such that the x-default item is the first item. - /// The SetLocalizedText function has several special features related to the x-default item, see - /// its description for details. The selection of the array item is the same for GetLocalizedText - /// and SetLocalizedText: - ///
    - ///
  • Look for an exact match with the specific language. - ///
  • If a generic language is given, look for a partial match. - ///
  • Look for an x-default item. - ///
  • Choose the first item. - ///
- /// A partial match with the generic language is where the start of the item's language matches - /// the generic string and the next character is '-'. An exact match is also recognized as a - /// degenerate case. It is fine to pass x-default as the specific language. In this case, - /// selection of an x-default item is an exact match by the first rule, not a selection by the - /// 3rd rule. The last 2 rules are fallbacks used when the specific and generic languages fail to - /// produce a match. getLocalizedText returns information about a selected item in - /// an alt-text array. The array item is selected according to the rules given above. - /// Note: In a future version of this API a method - /// using Java java.lang.Locale will be added. - ///
- /// - /// The namespace URI for the alt-text array. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the alt-text array. May be a general path expression, must not - /// be null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// - /// The name of the generic language as an RFC 3066 primary subtag. May be - /// null or the empty string if no generic language is wanted. - /// - /// - /// The name of the specific language as an RFC 3066 tag. Must not be - /// null or the empty string. - /// - /// - /// Returns an XMPProperty containing the value, the actual language and - /// the options if an appropriate alternate collection item exists, null - /// if the property. - /// does not exist. - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPProperty GetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang); - - /// Modifies the value of a selected item in an alt-text array. - /// - /// Modifies the value of a selected item in an alt-text array. Creates an appropriate array item - /// if necessary, and handles special cases for the x-default item. If the selected item is from - /// a match with the specific language, the value of that item is modified. If the existing value - /// of that item matches the existing value of the x-default item, the x-default item is also - /// modified. If the array only has 1 existing item (which is not x-default), an x-default item - /// is added with the given value. If the selected item is from a match with the generic language - /// and there are no other generic matches, the value of that item is modified. If the existing - /// value of that item matches the existing value of the x-default item, the x-default item is - /// also modified. If the array only has 1 existing item (which is not x-default), an x-default - /// item is added with the given value. If the selected item is from a partial match with the - /// generic language and there are other partial matches, a new item is created for the specific - /// language. The x-default item is not modified. If the selected item is from the last 2 rules - /// then a new item is created for the specific language. If the array only had an x-default - /// item, the x-default item is also modified. If the array was empty, items are created for the - /// specific language and x-default. - /// Note: In a future version of this API a method - /// using Java java.lang.Locale will be added. - /// - /// - /// The namespace URI for the alt-text array. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the alt-text array. May be a general path expression, must not - /// be null or the empty string. Has the same namespace prefix usage as - /// propName in getProperty(). - /// - /// - /// The name of the generic language as an RFC 3066 primary subtag. May be - /// null or the empty string if no generic language is wanted. - /// - /// - /// The name of the specific language as an RFC 3066 tag. Must not be - /// null or the empty string. - /// - /// - /// A pointer to the null terminated UTF-8 string that is the new - /// value for the appropriate array item. - /// - /// Option flags, none are defined at present. - /// Wraps all errors and exceptions that may occur. - /// - void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue, PropertyOptions options); - - /// - /// The namespace URI for the alt-text array - /// The name of the alt-text array - /// The name of the generic language - /// The name of the specific language - /// the new value for the appropriate array item - /// Wraps all errors and exceptions - /// - void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue); - - // --------------------------------------------------------------------------------------------- - // Functions accessing properties as binary values. - /// - /// These are very similar to getProperty() and SetProperty() above, - /// but the value is returned or provided in a literal form instead of as a UTF-8 string. - /// - /// - /// These are very similar to getProperty() and SetProperty() above, - /// but the value is returned or provided in a literal form instead of as a UTF-8 string. - /// The path composition functions in XMPPathFactory may be used to compose an path - /// expression for fields in nested structures, items in arrays, or qualifiers. - /// - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns a Boolean value or null - /// if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - bool GetPropertyBoolean(string schemaNS, string propName); - - /// Convenience method to retrieve the literal value of a property. - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns an Integer value or null - /// if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - int GetPropertyInteger(string schemaNS, string propName); - - /// Convenience method to retrieve the literal value of a property. - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns a Long value or null - /// if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - long GetPropertyLong(string schemaNS, string propName); - - /// Convenience method to retrieve the literal value of a property. - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns a Double value or null - /// if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - double GetPropertyDouble(string schemaNS, string propName); - - /// Convenience method to retrieve the literal value of a property. - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns a XMPDateTime-object or null - /// if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - XMPDateTime GetPropertyDate(string schemaNS, string propName); - - /// Convenience method to retrieve the literal value of a property. - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns a Java Calendar-object or null - /// if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - Calendar GetPropertyCalendar(string schemaNS, string propName); - - /// Convenience method to retrieve the literal value of a property. - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns a byte[]-array contained the decoded base64 value - /// or null if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - sbyte[] GetPropertyBase64(string schemaNS, string propName); - - /// Convenience method to retrieve the literal value of a property. - /// - /// Convenience method to retrieve the literal value of a property. - /// Note: There is no setPropertyString(), - /// because setProperty() sets a string value. - /// - /// - /// The namespace URI for the property. Has the same usage as in - /// getProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// - /// Returns a String value or null - /// if the property does not exist. - /// - /// - /// Wraps all exceptions that may occur, - /// especially conversion errors. - /// - /// - string GetPropertyString(string schemaNS, string propName); - - /// Convenience method to set a property to a literal boolean value. - /// - /// The namespace URI for the property. Has the same usage as in - /// setProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// the literal property value as boolean. - /// options of the property to set (optional). - /// Wraps all exceptions that may occur. - /// - void SetPropertyBoolean(string schemaNS, string propName, bool propValue, PropertyOptions options); - - /// - /// The namespace URI for the property - /// The name of the property - /// the literal property value as boolean - /// Wraps all exceptions - /// - void SetPropertyBoolean(string schemaNS, string propName, bool propValue); - - /// Convenience method to set a property to a literal int value. - /// - /// The namespace URI for the property. Has the same usage as in - /// setProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// the literal property value as int. - /// options of the property to set (optional). - /// Wraps all exceptions that may occur. - /// - void SetPropertyInteger(string schemaNS, string propName, int propValue, PropertyOptions options); - - /// - /// The namespace URI for the property - /// The name of the property - /// the literal property value as int - /// Wraps all exceptions - /// - void SetPropertyInteger(string schemaNS, string propName, int propValue); - - /// Convenience method to set a property to a literal long value. - /// - /// The namespace URI for the property. Has the same usage as in - /// setProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// the literal property value as long. - /// options of the property to set (optional). - /// Wraps all exceptions that may occur. - /// - void SetPropertyLong(string schemaNS, string propName, long propValue, PropertyOptions options); - - /// - /// The namespace URI for the property - /// The name of the property - /// the literal property value as long - /// Wraps all exceptions - /// - void SetPropertyLong(string schemaNS, string propName, long propValue); - - /// Convenience method to set a property to a literal double value. - /// - /// The namespace URI for the property. Has the same usage as in - /// setProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// the literal property value as double. - /// options of the property to set (optional). - /// Wraps all exceptions that may occur. - /// - void SetPropertyDouble(string schemaNS, string propName, double propValue, PropertyOptions options); - - /// - /// The namespace URI for the property - /// The name of the property - /// the literal property value as double - /// Wraps all exceptions - /// - void SetPropertyDouble(string schemaNS, string propName, double propValue); - - /// - /// Convenience method to set a property with an XMPDateTime-object, - /// which is serialized to an ISO8601 date. - /// - /// - /// The namespace URI for the property. Has the same usage as in - /// setProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// the property value as XMPDateTime. - /// options of the property to set (optional). - /// Wraps all exceptions that may occur. - /// - void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue, PropertyOptions options); - - /// - /// The namespace URI for the property - /// The name of the property - /// the property value as XMPDateTime - /// Wraps all exceptions - /// - void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue); - - /// - /// Convenience method to set a property with a Java Calendar-object, - /// which is serialized to an ISO8601 date. - /// - /// - /// The namespace URI for the property. Has the same usage as in - /// setProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// the property value as Java Calendar. - /// options of the property to set (optional). - /// Wraps all exceptions that may occur. - /// - void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue, PropertyOptions options); - - /// - /// The namespace URI for the property - /// The name of the property - /// the property value as Calendar - /// Wraps all exceptions - /// - void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue); - - /// - /// Convenience method to set a property from a binary byte[]-array, - /// which is serialized as base64-string. - /// - /// - /// The namespace URI for the property. Has the same usage as in - /// setProperty(). - /// - /// - /// The name of the property. - /// Has the same usage as in getProperty(). - /// - /// the literal property value as byte array. - /// options of the property to set (optional). - /// Wraps all exceptions that may occur. - /// - void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue, PropertyOptions options); - - /// - /// The namespace URI for the property - /// The name of the property - /// the literal property value as byte array - /// Wraps all exceptions - /// - void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue); - - /// Constructs an iterator for the properties within this XMP object. - /// Returns an XMPIterator. - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPIterator Iterator(); - - /// Constructs an iterator for the properties within this XMP object using some options. - /// Option flags to control the iteration. - /// Returns an XMPIterator. - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPIterator Iterator(IteratorOptions options); - - /// Construct an iterator for the properties within an XMP object. - /// - /// Construct an iterator for the properties within an XMP object. According to the parameters it iterates the entire data tree, - /// properties within a specific schema, or a subtree rooted at a specific node. - /// - /// - /// Optional schema namespace URI to restrict the iteration. Omitted (visit all - /// schema) by passing null or empty String. - /// - /// - /// Optional property name to restrict the iteration. May be an arbitrary path - /// expression. Omitted (visit all properties) by passing null or empty - /// String. If no schema URI is given, it is ignored. - /// - /// - /// Option flags to control the iteration. See - /// - /// for - /// details. - /// - /// - /// Returns an XMPIterator for this XMPMeta-object - /// considering the given options. - /// - /// Wraps all errors and exceptions that may occur. - /// - XMPIterator Iterator(string schemaNS, string propName, IteratorOptions options); - - /// - /// This correlates to the about-attribute, - /// returns the empty String if no name is set. - /// - /// Returns the name of the XMP object. - string GetObjectName(); - - /// Sets the name of the XMP object. - void SetObjectName(string name); - - /// - /// Returns the unparsed content of the <?xpacket> processing instruction. - /// This contains normally the attribute-like elements 'begin="<BOM>" - /// id="W5M0MpCehiHzreSzNTczkc9d"' and possibly the deprecated elements 'bytes="1234"' or - /// 'encoding="XXX"'. If the parsed packet has not been wrapped into an xpacket, - /// null is returned. - /// - string GetPacketHeader(); - - /// Clones the complete metadata tree. - /// Returns a deep copy of this instance. - object Clone(); - - /// - /// Sorts the complete datamodel according to the following rules: - ///
    - ///
  • Schema nodes are sorted by prefix. - ///
- /// - /// Sorts the complete datamodel according to the following rules: - ///
    - ///
  • Schema nodes are sorted by prefix. - ///
  • Properties at top level and within structs are sorted by full name, that is - /// prefix + local name. - ///
  • Array items are not sorted, even if they have no certain order such as bags. - ///
  • Qualifier are sorted, with the exception of "xml:lang" and/or "rdf:type" - /// that stay at the top of the list in that order. - ///
- ///
- void Sort(); - - /// Perform the normalization as a separate parsing step. - /// - /// Perform the normalization as a separate parsing step. - /// Normally it is done during parsing, unless the parsing option - /// - /// is set to true. - /// Note: It does no harm to call this method to an already normalized xmp object. - /// It was a PDF/A requirement to get hand on the unnormalized XMPMeta object. - /// - /// optional parsing options. - /// Wraps all errors and exceptions that may occur. - /// - void Normalize(ParseOptions options); - - /// Renders this node and the tree unter this node in a human readable form. - /// Returns a multiline string containing the dump. - string DumpObject(); - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using Com.Adobe.Xmp.Options; +using Com.Adobe.Xmp.Properties; +using Sharpen; + +namespace Com.Adobe.Xmp +{ + /// This class represents the set of XMP metadata as a DOM representation. + /// + /// This class represents the set of XMP metadata as a DOM representation. It has methods to read and + /// modify all kinds of properties, create an iterator over all properties and serialize the metadata + /// to a String, byte-array or OutputStream. + /// + /// 20.01.2006 + public interface XMPMeta : ICloneable + { + // --------------------------------------------------------------------------------------------- + // Basic property manipulation functions + /// + /// The property value getter-methods all take a property specification: the first two parameters + /// are always the top level namespace URI (the "schema" namespace) and the basic name + /// of the property being referenced. + /// + /// + /// The property value getter-methods all take a property specification: the first two parameters + /// are always the top level namespace URI (the "schema" namespace) and the basic name + /// of the property being referenced. See the introductory discussion of path expression usage + /// for more information. + ///

+ /// All of the functions return an object inherited from PropertyBase or + /// null if the property does not exists. The result object contains the value of + /// the property and option flags describing the property. Arrays and the non-leaf levels of + /// nodes do not have values. + ///

+ /// See + /// + /// for detailed information about the options. + ///

+ /// This is the simplest property getter, mainly for top level simple properties or after using + /// the path composition functions in XMPPathFactory. + /// + /// + /// The namespace URI for the property. May be null or the empty + /// string if the first component of the propName path contains a namespace prefix. The + /// URI must be for a registered namespace. + /// + /// + /// The name of the property. May be a general path expression, must not be + /// null or the empty string. Using a namespace prefix on the first + /// component is optional. If present without a schemaNS value then the prefix specifies + /// the namespace. The prefix must be for a registered namespace. If both a schemaNS URI + /// and propName prefix are present, they must be corresponding parts of a registered + /// namespace. + /// + /// + /// Returns a XMPProperty containing the value and the options or + /// null if the property does not exist. + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPProperty GetProperty(string schemaNS, string propName); + + ///

Provides access to items within an array. + /// + /// Provides access to items within an array. The index is passed as an integer, you need not + /// worry about the path string syntax for array items, convert a loop index to a string, etc. + /// + /// The namespace URI for the array. Has the same usage as in getProperty. + /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// + /// The index of the desired item. Arrays in XMP are indexed from 1. The + /// constant + /// + /// always refers to the last existing array + /// item. + /// + /// + /// Returns a XMPProperty containing the value and the options or + /// null if the property does not exist. + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPProperty GetArrayItem(string schemaNS, string arrayName, int itemIndex); + + /// Returns the number of items in the array. + /// The namespace URI for the array. Has the same usage as in getProperty. + /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// Returns the number of items in the array. + /// Wraps all errors and exceptions that may occur. + /// + int CountArrayItems(string schemaNS, string arrayName); + + /// Provides access to fields within a nested structure. + /// + /// Provides access to fields within a nested structure. The namespace for the field is passed as + /// a URI, you need not worry about the path string syntax. + ///

+ /// The names of fields should be XML qualified names, that is within an XML namespace. The path + /// syntax for a qualified name uses the namespace prefix. This is unreliable since the prefix is + /// never guaranteed. The URI is the formal name, the prefix is just a local shorthand in a given + /// sequence of XML text. + /// + /// The namespace URI for the struct. Has the same usage as in getProperty. + /// + /// The name of the struct. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// + /// The namespace URI for the field. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the field. Must be a single XML name, must not be + /// null or the empty string. Has the same namespace prefix usage as the + /// structName parameter. + /// + /// + /// Returns a XMPProperty containing the value and the options or + /// null if the property does not exist. Arrays and non-leaf levels of + /// structs do not have values. + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPProperty GetStructField(string schemaNS, string structName, string fieldNS, string fieldName); + + ///

Provides access to a qualifier attached to a property. + /// + /// Provides access to a qualifier attached to a property. The namespace for the qualifier is + /// passed as a URI, you need not worry about the path string syntax. In many regards qualifiers + /// are like struct fields. See the introductory discussion of qualified properties for more + /// information. + ///

+ /// The names of qualifiers should be XML qualified names, that is within an XML namespace. The + /// path syntax for a qualified name uses the namespace prefix. This is unreliable since the + /// prefix is never guaranteed. The URI is the formal name, the prefix is just a local shorthand + /// in a given sequence of XML text. + ///

+ /// Note: Qualifiers are only supported for simple leaf properties at this time. + /// + /// The namespace URI for the struct. Has the same usage as in getProperty. + /// + /// The name of the property to which the qualifier is attached. May be a general + /// path expression, must not be null or the empty string. Has the same + /// namespace prefix usage as in getProperty(). + /// + /// + /// The namespace URI for the qualifier. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the qualifier. Must be a single XML name, must not be + /// null or the empty string. Has the same namespace prefix usage as the + /// propName parameter. + /// + /// + /// Returns a XMPProperty containing the value and the options of the + /// qualifier or null if the property does not exist. The name of the + /// qualifier must be a single XML name, must not be null or the empty + /// string. Has the same namespace prefix usage as the propName parameter. + ///

+ /// The value of the qualifier is only set if it has one (Arrays and non-leaf levels of + /// structs do not have values). + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPProperty GetQualifier(string schemaNS, string propName, string qualNS, string qualName); + + // --------------------------------------------------------------------------------------------- + // Functions for setting property values + ///

+ /// The property value setters all take a property specification, their + /// differences are in the form of this. + /// + /// + /// The property value setters all take a property specification, their + /// differences are in the form of this. The first two parameters are always the top level + /// namespace URI (the schema namespace) and the basic name of the property being + /// referenced. See the introductory discussion of path expression usage for more information. + ///

+ /// All of the functions take a string value for the property and option flags describing the + /// property. The value must be Unicode in UTF-8 encoding. Arrays and non-leaf levels of structs + /// do not have values. Empty arrays and structs may be created using appropriate option flags. + /// All levels of structs that is assigned implicitly are created if necessary. appendArayItem + /// implicitly creates the named array if necessary. + ///

+ /// See + /// + /// for detailed information about the options. + ///

+ /// This is the simplest property setter, mainly for top level simple properties or after using + /// the path composition functions in + /// + /// . + /// + /// The namespace URI for the property. Has the same usage as in getProperty. + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// the value for the property (only leaf properties have a value). + /// Arrays and non-leaf levels of structs do not have values. + /// Must be null if the value is not relevant.
+ /// The value is automatically detected: Boolean, Integer, Long, Double, XMPDateTime and + /// byte[] are handled, on all other toString() is called. + /// + /// Option flags describing the property. See the earlier description. + /// Wraps all errors and exceptions that may occur. + /// + void SetProperty(string schemaNS, string propName, object propValue, PropertyOptions options); + + /// + /// The namespace URI + /// The name of the property + /// the value for the property + /// Wraps all errors and exceptions + /// + void SetProperty(string schemaNS, string propName, object propValue); + + ///

Replaces an item within an array. + /// + /// Replaces an item within an array. The index is passed as an integer, you need not worry about + /// the path string syntax for array items, convert a loop index to a string, etc. The array + /// passed must already exist. In normal usage the selected array item is modified. A new item is + /// automatically appended if the index is the array size plus 1. + /// + /// The namespace URI for the array. Has the same usage as in getProperty. + /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty. + /// + /// + /// The index of the desired item. Arrays in XMP are indexed from 1. To address + /// the last existing item, use + /// + /// to find + /// out the length of the array. + /// + /// + /// the new value of the array item. Has the same usage as propValue in + /// setProperty(). + /// + /// the set options for the item. + /// Wraps all errors and exceptions that may occur. + /// + void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options); + + /// + /// The namespace URI + /// The name of the array + /// The index to insert the new item + /// the new value of the array item + /// Wraps all errors and exceptions + /// + void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue); + + /// Inserts an item into an array previous to the given index. + /// + /// Inserts an item into an array previous to the given index. The index is passed as an integer, + /// you need not worry about the path string syntax for array items, convert a loop index to a + /// string, etc. The array passed must already exist. In normal usage the selected array item is + /// modified. A new item is automatically appended if the index is the array size plus 1. + /// + /// The namespace URI for the array. Has the same usage as in getProperty. + /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty. + /// + /// + /// The index to insert the new item. Arrays in XMP are indexed from 1. Use + /// XMPConst.ARRAY_LAST_ITEM to append items. + /// + /// + /// the new value of the array item. Has the same usage as + /// propValue in setProperty(). + /// + /// the set options that decide about the kind of the node. + /// Wraps all errors and exceptions that may occur. + /// + void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options); + + /// + /// The namespace URI for the array + /// The name of the array + /// The index to insert the new item + /// the value of the array item + /// Wraps all errors and exceptions + /// + void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue); + + /// Simplifies the construction of an array by not requiring that you pre-create an empty array. + /// + /// Simplifies the construction of an array by not requiring that you pre-create an empty array. + /// The array that is assigned is created automatically if it does not yet exist. Each call to + /// appendArrayItem() appends an item to the array. The corresponding parameters have the same + /// use as setArrayItem(). The arrayOptions parameter is used to specify what kind of array. If + /// the array exists, it must have the specified form. + /// + /// The namespace URI for the array. Has the same usage as in getProperty. + /// + /// The name of the array. May be a general path expression, must not be null or + /// the empty string. Has the same namespace prefix usage as propPath in getProperty. + /// + /// + /// Option flags describing the array form. The only valid options are + ///
    + ///
  • + /// + /// , + ///
  • + /// + /// , + ///
  • + /// + /// or + ///
  • + /// + /// . + ///
+ /// Note: the array options only need to be provided if the array is not + /// already existing, otherwise you can set them to null or use + /// + /// . + /// + /// the value of the array item. Has the same usage as propValue in getProperty. + /// + /// Option flags describing the item to append ( + /// + /// ) + /// + /// Wraps all errors and exceptions that may occur. + /// + void AppendArrayItem(string schemaNS, string arrayName, PropertyOptions arrayOptions, string itemValue, PropertyOptions itemOptions); + + /// + /// The namespace URI for the array + /// The name of the array + /// the value of the array item + /// Wraps all errors and exceptions + /// + void AppendArrayItem(string schemaNS, string arrayName, string itemValue); + + /// Provides access to fields within a nested structure. + /// + /// Provides access to fields within a nested structure. The namespace for the field is passed as + /// a URI, you need not worry about the path string syntax. The names of fields should be XML + /// qualified names, that is within an XML namespace. The path syntax for a qualified name uses + /// the namespace prefix, which is unreliable because the prefix is never guaranteed. The URI is + /// the formal name, the prefix is just a local shorthand in a given sequence of XML text. + /// + /// The namespace URI for the struct. Has the same usage as in getProperty. + /// + /// The name of the struct. May be a general path expression, must not be null + /// or the empty string. Has the same namespace prefix usage as propName in getProperty. + /// + /// + /// The namespace URI for the field. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the field. Must be a single XML name, must not be null or the + /// empty string. Has the same namespace prefix usage as the structName parameter. + /// + /// + /// the value of thefield, if the field has a value. + /// Has the same usage as propValue in getProperty. + /// + /// Option flags describing the field. See the earlier description. + /// Wraps all errors and exceptions that may occur. + /// + void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue, PropertyOptions options); + + /// + /// The namespace URI for the struct + /// The name of the struct + /// The namespace URI for the field + /// The name of the field + /// the value of the field + /// Wraps all errors and exceptions + /// + void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue); + + /// Provides access to a qualifier attached to a property. + /// + /// Provides access to a qualifier attached to a property. The namespace for the qualifier is + /// passed as a URI, you need not worry about the path string syntax. In many regards qualifiers + /// are like struct fields. See the introductory discussion of qualified properties for more + /// information. The names of qualifiers should be XML qualified names, that is within an XML + /// namespace. The path syntax for a qualified name uses the namespace prefix, which is + /// unreliable because the prefix is never guaranteed. The URI is the formal name, the prefix is + /// just a local shorthand in a given sequence of XML text. The property the qualifier + /// will be attached has to exist. + /// + /// The namespace URI for the struct. Has the same usage as in getProperty. + /// + /// The name of the property to which the qualifier is attached. Has the same + /// usage as in getProperty. + /// + /// + /// The namespace URI for the qualifier. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the qualifier. Must be a single XML name, must not be + /// null or the empty string. Has the same namespace prefix usage as the + /// propName parameter. + /// + /// + /// A pointer to the null terminated UTF-8 string that is the + /// value of the qualifier, if the qualifier has a value. Has the same usage as propValue + /// in getProperty. + /// + /// Option flags describing the qualifier. See the earlier description. + /// Wraps all errors and exceptions that may occur. + /// + void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue, PropertyOptions options); + + /// + /// The namespace URI for the struct + /// The name of the property to which the qualifier is attached + /// The namespace URI for the qualifier + /// The name of the qualifier + /// the value of the qualifier + /// Wraps all errors and exceptions + /// + void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue); + + // --------------------------------------------------------------------------------------------- + // Functions for deleting and detecting properties. These should be obvious from the + // descriptions of the getters and setters. + /// Deletes the given XMP subtree rooted at the given property. + /// + /// Deletes the given XMP subtree rooted at the given property. It is not an error if the + /// property does not exist. + /// + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// The name of the property. Has the same usage as in getProperty. + void DeleteProperty(string schemaNS, string propName); + + /// Deletes the given XMP subtree rooted at the given array item. + /// + /// Deletes the given XMP subtree rooted at the given array item. It is not an error if the array + /// item does not exist. + /// + /// The namespace URI for the array. Has the same usage as in getProperty. + /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// + /// The index of the desired item. Arrays in XMP are indexed from 1. The + /// constant XMPConst.ARRAY_LAST_ITEM always refers to the last + /// existing array item. + /// + void DeleteArrayItem(string schemaNS, string arrayName, int itemIndex); + + /// Deletes the given XMP subtree rooted at the given struct field. + /// + /// Deletes the given XMP subtree rooted at the given struct field. It is not an error if the + /// field does not exist. + /// + /// + /// The namespace URI for the struct. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the struct. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty. + /// + /// + /// The namespace URI for the field. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the field. Must be a single XML name, must not be + /// null or the empty string. Has the same namespace prefix usage as the + /// structName parameter. + /// + void DeleteStructField(string schemaNS, string structName, string fieldNS, string fieldName); + + /// Deletes the given XMP subtree rooted at the given qualifier. + /// + /// Deletes the given XMP subtree rooted at the given qualifier. It is not an error if the + /// qualifier does not exist. + /// + /// + /// The namespace URI for the struct. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property to which the qualifier is attached. Has the same + /// usage as in getProperty. + /// + /// + /// The namespace URI for the qualifier. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the qualifier. Must be a single XML name, must not be + /// null or the empty string. Has the same namespace prefix usage as the + /// propName parameter. + /// + void DeleteQualifier(string schemaNS, string propName, string qualNS, string qualName); + + /// Returns whether the property exists. + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// Returns true if the property exists. + bool DoesPropertyExist(string schemaNS, string propName); + + /// Tells if the array item exists. + /// + /// The namespace URI for the array. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// + /// The index of the desired item. Arrays in XMP are indexed from 1. The + /// constant XMPConst.ARRAY_LAST_ITEM always refers to the last + /// existing array item. + /// + /// Returns true if the array exists, false otherwise. + bool DoesArrayItemExist(string schemaNS, string arrayName, int itemIndex); + + /// DoesStructFieldExist tells if the struct field exists. + /// + /// The namespace URI for the struct. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the struct. May be a general path expression, must not be + /// null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// + /// The namespace URI for the field. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the field. Must be a single XML name, must not be + /// null or the empty string. Has the same namespace prefix usage as the + /// structName parameter. + /// + /// Returns true if the field exists. + bool DoesStructFieldExist(string schemaNS, string structName, string fieldNS, string fieldName); + + /// DoesQualifierExist tells if the qualifier exists. + /// + /// The namespace URI for the struct. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property to which the qualifier is attached. Has the same + /// usage as in getProperty(). + /// + /// + /// The namespace URI for the qualifier. Has the same URI and prefix usage as the + /// schemaNS parameter. + /// + /// + /// The name of the qualifier. Must be a single XML name, must not be + /// null or the empty string. Has the same namespace prefix usage as the + /// propName parameter. + /// + /// Returns true if the qualifier exists. + bool DoesQualifierExist(string schemaNS, string propName, string qualNS, string qualName); + + // --------------------------------------------------------------------------------------------- + // Specialized Get and Set functions + /// + /// These functions provide convenient support for localized text properties, including a number + /// of special and obscure aspects. + /// + /// + /// These functions provide convenient support for localized text properties, including a number + /// of special and obscure aspects. Localized text properties are stored in alt-text arrays. They + /// allow multiple concurrent localizations of a property value, for example a document title or + /// copyright in several languages. The most important aspect of these functions is that they + /// select an appropriate array item based on one or two RFC 3066 language tags. One of these + /// languages, the "specific" language, is preferred and selected if there is an exact match. For + /// many languages it is also possible to define a "generic" language that may be used if there + /// is no specific language match. The generic language must be a valid RFC 3066 primary subtag, + /// or the empty string. For example, a specific language of "en-US" should be used in the US, + /// and a specific language of "en-UK" should be used in England. It is also appropriate to use + /// "en" as the generic language in each case. If a US document goes to England, the "en-US" + /// title is selected by using the "en" generic language and the "en-UK" specific language. It is + /// considered poor practice, but allowed, to pass a specific language that is just an RFC 3066 + /// primary tag. For example "en" is not a good specific language, it should only be used as a + /// generic language. Passing "i" or "x" as the generic language is also considered poor practice + /// but allowed. Advice from the W3C about the use of RFC 3066 language tags can be found at: + /// http://www.w3.org/International/articles/language-tags/ + ///

+ /// Note: RFC 3066 language tags must be treated in a case insensitive manner. The XMP + /// Toolkit does this by normalizing their capitalization: + ///

    + ///
  • The primary subtag is lower case, the suggested practice of ISO 639. + ///
  • All 2 letter secondary subtags are upper case, the suggested practice of ISO 3166. + ///
  • All other subtags are lower case. The XMP specification defines an artificial language, + ///
  • "x-default", that is used to explicitly denote a default item in an alt-text array. + ///
+ /// The XMP toolkit normalizes alt-text arrays such that the x-default item is the first item. + /// The SetLocalizedText function has several special features related to the x-default item, see + /// its description for details. The selection of the array item is the same for GetLocalizedText + /// and SetLocalizedText: + ///
    + ///
  • Look for an exact match with the specific language. + ///
  • If a generic language is given, look for a partial match. + ///
  • Look for an x-default item. + ///
  • Choose the first item. + ///
+ /// A partial match with the generic language is where the start of the item's language matches + /// the generic string and the next character is '-'. An exact match is also recognized as a + /// degenerate case. It is fine to pass x-default as the specific language. In this case, + /// selection of an x-default item is an exact match by the first rule, not a selection by the + /// 3rd rule. The last 2 rules are fallbacks used when the specific and generic languages fail to + /// produce a match. getLocalizedText returns information about a selected item in + /// an alt-text array. The array item is selected according to the rules given above. + /// Note: In a future version of this API a method + /// using Java java.lang.Locale will be added. + ///
+ /// + /// The namespace URI for the alt-text array. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the alt-text array. May be a general path expression, must not + /// be null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// + /// The name of the generic language as an RFC 3066 primary subtag. May be + /// null or the empty string if no generic language is wanted. + /// + /// + /// The name of the specific language as an RFC 3066 tag. Must not be + /// null or the empty string. + /// + /// + /// Returns an XMPProperty containing the value, the actual language and + /// the options if an appropriate alternate collection item exists, null + /// if the property. + /// does not exist. + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPProperty GetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang); + + /// Modifies the value of a selected item in an alt-text array. + /// + /// Modifies the value of a selected item in an alt-text array. Creates an appropriate array item + /// if necessary, and handles special cases for the x-default item. If the selected item is from + /// a match with the specific language, the value of that item is modified. If the existing value + /// of that item matches the existing value of the x-default item, the x-default item is also + /// modified. If the array only has 1 existing item (which is not x-default), an x-default item + /// is added with the given value. If the selected item is from a match with the generic language + /// and there are no other generic matches, the value of that item is modified. If the existing + /// value of that item matches the existing value of the x-default item, the x-default item is + /// also modified. If the array only has 1 existing item (which is not x-default), an x-default + /// item is added with the given value. If the selected item is from a partial match with the + /// generic language and there are other partial matches, a new item is created for the specific + /// language. The x-default item is not modified. If the selected item is from the last 2 rules + /// then a new item is created for the specific language. If the array only had an x-default + /// item, the x-default item is also modified. If the array was empty, items are created for the + /// specific language and x-default. + /// Note: In a future version of this API a method + /// using Java java.lang.Locale will be added. + /// + /// + /// The namespace URI for the alt-text array. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the alt-text array. May be a general path expression, must not + /// be null or the empty string. Has the same namespace prefix usage as + /// propName in getProperty(). + /// + /// + /// The name of the generic language as an RFC 3066 primary subtag. May be + /// null or the empty string if no generic language is wanted. + /// + /// + /// The name of the specific language as an RFC 3066 tag. Must not be + /// null or the empty string. + /// + /// + /// A pointer to the null terminated UTF-8 string that is the new + /// value for the appropriate array item. + /// + /// Option flags, none are defined at present. + /// Wraps all errors and exceptions that may occur. + /// + void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue, PropertyOptions options); + + /// + /// The namespace URI for the alt-text array + /// The name of the alt-text array + /// The name of the generic language + /// The name of the specific language + /// the new value for the appropriate array item + /// Wraps all errors and exceptions + /// + void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue); + + // --------------------------------------------------------------------------------------------- + // Functions accessing properties as binary values. + /// + /// These are very similar to getProperty() and SetProperty() above, + /// but the value is returned or provided in a literal form instead of as a UTF-8 string. + /// + /// + /// These are very similar to getProperty() and SetProperty() above, + /// but the value is returned or provided in a literal form instead of as a UTF-8 string. + /// The path composition functions in XMPPathFactory may be used to compose an path + /// expression for fields in nested structures, items in arrays, or qualifiers. + /// + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns a Boolean value or null + /// if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + bool GetPropertyBoolean(string schemaNS, string propName); + + /// Convenience method to retrieve the literal value of a property. + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns an Integer value or null + /// if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + int GetPropertyInteger(string schemaNS, string propName); + + /// Convenience method to retrieve the literal value of a property. + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns a Long value or null + /// if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + long GetPropertyLong(string schemaNS, string propName); + + /// Convenience method to retrieve the literal value of a property. + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns a Double value or null + /// if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + double GetPropertyDouble(string schemaNS, string propName); + + /// Convenience method to retrieve the literal value of a property. + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns a XMPDateTime-object or null + /// if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + XMPDateTime GetPropertyDate(string schemaNS, string propName); + + /// Convenience method to retrieve the literal value of a property. + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns a Java Calendar-object or null + /// if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + Calendar GetPropertyCalendar(string schemaNS, string propName); + + /// Convenience method to retrieve the literal value of a property. + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns a byte[]-array contained the decoded base64 value + /// or null if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + sbyte[] GetPropertyBase64(string schemaNS, string propName); + + /// Convenience method to retrieve the literal value of a property. + /// + /// Convenience method to retrieve the literal value of a property. + /// Note: There is no setPropertyString(), + /// because setProperty() sets a string value. + /// + /// + /// The namespace URI for the property. Has the same usage as in + /// getProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// + /// Returns a String value or null + /// if the property does not exist. + /// + /// + /// Wraps all exceptions that may occur, + /// especially conversion errors. + /// + /// + string GetPropertyString(string schemaNS, string propName); + + /// Convenience method to set a property to a literal boolean value. + /// + /// The namespace URI for the property. Has the same usage as in + /// setProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// the literal property value as boolean. + /// options of the property to set (optional). + /// Wraps all exceptions that may occur. + /// + void SetPropertyBoolean(string schemaNS, string propName, bool propValue, PropertyOptions options); + + /// + /// The namespace URI for the property + /// The name of the property + /// the literal property value as boolean + /// Wraps all exceptions + /// + void SetPropertyBoolean(string schemaNS, string propName, bool propValue); + + /// Convenience method to set a property to a literal int value. + /// + /// The namespace URI for the property. Has the same usage as in + /// setProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// the literal property value as int. + /// options of the property to set (optional). + /// Wraps all exceptions that may occur. + /// + void SetPropertyInteger(string schemaNS, string propName, int propValue, PropertyOptions options); + + /// + /// The namespace URI for the property + /// The name of the property + /// the literal property value as int + /// Wraps all exceptions + /// + void SetPropertyInteger(string schemaNS, string propName, int propValue); + + /// Convenience method to set a property to a literal long value. + /// + /// The namespace URI for the property. Has the same usage as in + /// setProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// the literal property value as long. + /// options of the property to set (optional). + /// Wraps all exceptions that may occur. + /// + void SetPropertyLong(string schemaNS, string propName, long propValue, PropertyOptions options); + + /// + /// The namespace URI for the property + /// The name of the property + /// the literal property value as long + /// Wraps all exceptions + /// + void SetPropertyLong(string schemaNS, string propName, long propValue); + + /// Convenience method to set a property to a literal double value. + /// + /// The namespace URI for the property. Has the same usage as in + /// setProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// the literal property value as double. + /// options of the property to set (optional). + /// Wraps all exceptions that may occur. + /// + void SetPropertyDouble(string schemaNS, string propName, double propValue, PropertyOptions options); + + /// + /// The namespace URI for the property + /// The name of the property + /// the literal property value as double + /// Wraps all exceptions + /// + void SetPropertyDouble(string schemaNS, string propName, double propValue); + + /// + /// Convenience method to set a property with an XMPDateTime-object, + /// which is serialized to an ISO8601 date. + /// + /// + /// The namespace URI for the property. Has the same usage as in + /// setProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// the property value as XMPDateTime. + /// options of the property to set (optional). + /// Wraps all exceptions that may occur. + /// + void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue, PropertyOptions options); + + /// + /// The namespace URI for the property + /// The name of the property + /// the property value as XMPDateTime + /// Wraps all exceptions + /// + void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue); + + /// + /// Convenience method to set a property with a Java Calendar-object, + /// which is serialized to an ISO8601 date. + /// + /// + /// The namespace URI for the property. Has the same usage as in + /// setProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// the property value as Java Calendar. + /// options of the property to set (optional). + /// Wraps all exceptions that may occur. + /// + void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue, PropertyOptions options); + + /// + /// The namespace URI for the property + /// The name of the property + /// the property value as Calendar + /// Wraps all exceptions + /// + void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue); + + /// + /// Convenience method to set a property from a binary byte[]-array, + /// which is serialized as base64-string. + /// + /// + /// The namespace URI for the property. Has the same usage as in + /// setProperty(). + /// + /// + /// The name of the property. + /// Has the same usage as in getProperty(). + /// + /// the literal property value as byte array. + /// options of the property to set (optional). + /// Wraps all exceptions that may occur. + /// + void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue, PropertyOptions options); + + /// + /// The namespace URI for the property + /// The name of the property + /// the literal property value as byte array + /// Wraps all exceptions + /// + void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue); + + /// Constructs an iterator for the properties within this XMP object. + /// Returns an XMPIterator. + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPIterator Iterator(); + + /// Constructs an iterator for the properties within this XMP object using some options. + /// Option flags to control the iteration. + /// Returns an XMPIterator. + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPIterator Iterator(IteratorOptions options); + + /// Construct an iterator for the properties within an XMP object. + /// + /// Construct an iterator for the properties within an XMP object. According to the parameters it iterates the entire data tree, + /// properties within a specific schema, or a subtree rooted at a specific node. + /// + /// + /// Optional schema namespace URI to restrict the iteration. Omitted (visit all + /// schema) by passing null or empty String. + /// + /// + /// Optional property name to restrict the iteration. May be an arbitrary path + /// expression. Omitted (visit all properties) by passing null or empty + /// String. If no schema URI is given, it is ignored. + /// + /// + /// Option flags to control the iteration. See + /// + /// for + /// details. + /// + /// + /// Returns an XMPIterator for this XMPMeta-object + /// considering the given options. + /// + /// Wraps all errors and exceptions that may occur. + /// + XMPIterator Iterator(string schemaNS, string propName, IteratorOptions options); + + /// + /// This correlates to the about-attribute, + /// returns the empty String if no name is set. + /// + /// Returns the name of the XMP object. + string GetObjectName(); + + /// Sets the name of the XMP object. + void SetObjectName(string name); + + /// + /// Returns the unparsed content of the <?xpacket> processing instruction. + /// This contains normally the attribute-like elements 'begin="<BOM>" + /// id="W5M0MpCehiHzreSzNTczkc9d"' and possibly the deprecated elements 'bytes="1234"' or + /// 'encoding="XXX"'. If the parsed packet has not been wrapped into an xpacket, + /// null is returned. + /// + string GetPacketHeader(); + + /// Clones the complete metadata tree. + /// Returns a deep copy of this instance. + object Clone(); + + /// + /// Sorts the complete datamodel according to the following rules: + ///
    + ///
  • Schema nodes are sorted by prefix. + ///
+ /// + /// Sorts the complete datamodel according to the following rules: + ///
    + ///
  • Schema nodes are sorted by prefix. + ///
  • Properties at top level and within structs are sorted by full name, that is + /// prefix + local name. + ///
  • Array items are not sorted, even if they have no certain order such as bags. + ///
  • Qualifier are sorted, with the exception of "xml:lang" and/or "rdf:type" + /// that stay at the top of the list in that order. + ///
+ ///
+ void Sort(); + + /// Perform the normalization as a separate parsing step. + /// + /// Perform the normalization as a separate parsing step. + /// Normally it is done during parsing, unless the parsing option + /// + /// is set to true. + /// Note: It does no harm to call this method to an already normalized xmp object. + /// It was a PDF/A requirement to get hand on the unnormalized XMPMeta object. + /// + /// optional parsing options. + /// Wraps all errors and exceptions that may occur. + /// + void Normalize(ParseOptions options); + + /// Renders this node and the tree unter this node in a human readable form. + /// Returns a multiline string containing the dump. + string DumpObject(); + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPMetaFactory.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPMetaFactory.cs index 7c568b881..bcf471bee 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPMetaFactory.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPMetaFactory.cs @@ -1,312 +1,312 @@ -//================================================================================================= -//ADOBE SYSTEMS INCORPORATED -//Copyright 2006-2007 Adobe Systems Incorporated -//All Rights Reserved -// -//NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -//of the Adobe license agreement accompanying it. -//================================================================================================= - -using System; -using Com.Adobe.Xmp.Impl; -using Com.Adobe.Xmp.Options; -using Sharpen; - -namespace Com.Adobe.Xmp -{ - /// Creates XMPMeta-instances from an InputStream - /// 30.01.2006 - public sealed class XMPMetaFactory - { - /// The singleton instance of the XMPSchemaRegistry. - private static XMPSchemaRegistry schema = new XMPSchemaRegistryImpl(); - - /// cache for version info - private static XMPVersionInfo versionInfo = null; - - /// Hides public constructor - private XMPMetaFactory() - { - } - - // EMPTY - /// Returns the singleton instance of the XMPSchemaRegistry. - public static XMPSchemaRegistry GetSchemaRegistry() - { - return schema; - } - - /// Returns an empty XMPMeta-object. - public static XMPMeta Create() - { - return new XMPMetaImpl(); - } - - /// Parsing with default options. - /// - /// an InputStream - /// Returns the XMPMeta-object created from the input. - /// If the file is not well-formed XML or if the parsing fails. - /// - public static XMPMeta Parse(InputStream @in) - { - return Parse(@in, null); - } - - /// - /// These functions support parsing serialized RDF into an XMP object, and serailizing an XMP - /// object into RDF. - /// - /// - /// These functions support parsing serialized RDF into an XMP object, and serailizing an XMP - /// object into RDF. The input for parsing may be any valid Unicode - /// encoding. ISO Latin-1 is also recognized, but its use is strongly discouraged. Serialization - /// is always as UTF-8. - ///

- /// parseFromBuffer() parses RDF from an InputStream. The encoding - /// is recognized automatically. - /// - /// an InputStream - /// - /// Options controlling the parsing.
- /// The available options are: - ///

    - ///
  • XMP_REQUIRE_XMPMETA - The <x:xmpmeta> XML element is required around - /// <rdf:RDF>. - ///
  • XMP_STRICT_ALIASING - Do not reconcile alias differences, throw an exception. - ///
- /// Note:The XMP_STRICT_ALIASING option is not yet implemented. - /// - /// Returns the XMPMeta-object created from the input. - /// If the file is not well-formed XML or if the parsing fails. - /// - public static XMPMeta Parse(InputStream @in, ParseOptions options) - { - return XMPMetaParser.Parse(@in, options); - } - - /// Parsing with default options. - /// - /// a String contain an XMP-file. - /// Returns the XMPMeta-object created from the input. - /// If the file is not well-formed XML or if the parsing fails. - /// - public static XMPMeta ParseFromString(string packet) - { - return ParseFromString(packet, null); - } - - /// Creates an XMPMeta-object from a string. - /// - /// a String contain an XMP-file. - /// Options controlling the parsing. - /// Returns the XMPMeta-object created from the input. - /// If the file is not well-formed XML or if the parsing fails. - /// - public static XMPMeta ParseFromString(string packet, ParseOptions options) - { - return XMPMetaParser.Parse(packet, options); - } - - /// Parsing with default options. - /// - /// a String contain an XMP-file. - /// Returns the XMPMeta-object created from the input. - /// If the file is not well-formed XML or if the parsing fails. - /// - public static XMPMeta ParseFromBuffer(sbyte[] buffer) - { - return ParseFromBuffer(buffer, null); - } - - /// Creates an XMPMeta-object from a byte-buffer. - /// - /// a String contain an XMP-file. - /// Options controlling the parsing. - /// Returns the XMPMeta-object created from the input. - /// If the file is not well-formed XML or if the parsing fails. - /// - public static XMPMeta ParseFromBuffer(sbyte[] buffer, ParseOptions options) - { - return XMPMetaParser.Parse(buffer, options); - } - - /// - /// Serializes an XMPMeta-object as RDF into an OutputStream - /// with default options. - /// - /// a metadata object - /// an OutputStream to write the serialized RDF to. - /// on serializsation errors. - /// - public static void Serialize(XMPMeta xmp, OutputStream @out) - { - Serialize(xmp, @out, null); - } - - /// Serializes an XMPMeta-object as RDF into an OutputStream. - /// a metadata object - /// - /// Options to control the serialization (see - /// - /// ). - /// - /// an OutputStream to write the serialized RDF to. - /// on serializsation errors. - /// - public static void Serialize(XMPMeta xmp, OutputStream @out, SerializeOptions options) - { - AssertImplementation(xmp); - XMPSerializerHelper.Serialize((XMPMetaImpl)xmp, @out, options); - } - - /// Serializes an XMPMeta-object as RDF into a byte buffer. - /// a metadata object - /// - /// Options to control the serialization (see - /// - /// ). - /// - /// Returns a byte buffer containing the serialized RDF. - /// on serializsation errors. - /// - public static sbyte[] SerializeToBuffer(XMPMeta xmp, SerializeOptions options) - { - AssertImplementation(xmp); - return XMPSerializerHelper.SerializeToBuffer((XMPMetaImpl)xmp, options); - } - - /// Serializes an XMPMeta-object as RDF into a string. - /// - /// Serializes an XMPMeta-object as RDF into a string. Note: Encoding - /// is ignored when serializing to a string. - /// - /// a metadata object - /// - /// Options to control the serialization (see - /// - /// ). - /// - /// Returns a string containing the serialized RDF. - /// on serializsation errors. - /// - public static string SerializeToString(XMPMeta xmp, SerializeOptions options) - { - AssertImplementation(xmp); - return XMPSerializerHelper.SerializeToString((XMPMetaImpl)xmp, options); - } - - /// Asserts that xmp is compatible to XMPMetaImpl.s - private static void AssertImplementation(XMPMeta xmp) - { - if (!(xmp is XMPMetaImpl)) - { - throw new NotSupportedException("The serializing service works only" + "with the XMPMeta implementation of this library"); - } - } - - /// Resets the schema registry to its original state (creates a new one). - /// - /// Resets the schema registry to its original state (creates a new one). - /// Be careful this might break all existing XMPMeta-objects and should be used - /// only for testing purpurses. - /// - public static void Reset() - { - schema = new XMPSchemaRegistryImpl(); - } - - /// Obtain version information. - /// - /// Obtain version information. The XMPVersionInfo singleton is created the first time - /// its requested. - /// - /// Returns the version information. - public static XMPVersionInfo GetVersionInfo() - { - lock (typeof(XMPMetaFactory)) - { - if (versionInfo == null) - { - try - { - int major = 5; - int minor = 1; - int micro = 0; - int engBuild = 3; - bool debug = false; - // Adobe XMP Core 5.0-jc001 DEBUG-., 2009 Jan 28 15:22:38-CET - string message = "Adobe XMP Core 5.1.0-jc003"; - versionInfo = new _XMPVersionInfo_274(major, minor, micro, debug, engBuild, message); - } - catch (Exception e) - { - // EMTPY, severe error would be detected during the tests - Console.Out.Println(e); - } - } - return versionInfo; - } - } - - private sealed class _XMPVersionInfo_274 : XMPVersionInfo - { - public _XMPVersionInfo_274(int major, int minor, int micro, bool debug, int engBuild, string message) - { - this.major = major; - this.minor = minor; - this.micro = micro; - this.debug = debug; - this.engBuild = engBuild; - this.message = message; - } - - public int GetMajor() - { - return major; - } - - public int GetMinor() - { - return minor; - } - - public int GetMicro() - { - return micro; - } - - public bool IsDebug() - { - return debug; - } - - public int GetBuild() - { - return engBuild; - } - - public string GetMessage() - { - return message; - } - - public override string ToString() - { - return message; - } - - private readonly int major; - - private readonly int minor; - - private readonly int micro; - - private readonly bool debug; - - private readonly int engBuild; - - private readonly string message; - } - } -} +//================================================================================================= +//ADOBE SYSTEMS INCORPORATED +//Copyright 2006-2007 Adobe Systems Incorporated +//All Rights Reserved +// +//NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +//of the Adobe license agreement accompanying it. +//================================================================================================= + +using System; +using Com.Adobe.Xmp.Impl; +using Com.Adobe.Xmp.Options; +using Sharpen; + +namespace Com.Adobe.Xmp +{ + /// Creates XMPMeta-instances from an InputStream + /// 30.01.2006 + public sealed class XMPMetaFactory + { + /// The singleton instance of the XMPSchemaRegistry. + private static XMPSchemaRegistry schema = new XMPSchemaRegistryImpl(); + + /// cache for version info + private static XMPVersionInfo versionInfo = null; + + /// Hides public constructor + private XMPMetaFactory() + { + } + + // EMPTY + /// Returns the singleton instance of the XMPSchemaRegistry. + public static XMPSchemaRegistry GetSchemaRegistry() + { + return schema; + } + + /// Returns an empty XMPMeta-object. + public static XMPMeta Create() + { + return new XMPMetaImpl(); + } + + /// Parsing with default options. + /// + /// an InputStream + /// Returns the XMPMeta-object created from the input. + /// If the file is not well-formed XML or if the parsing fails. + /// + public static XMPMeta Parse(InputStream @in) + { + return Parse(@in, null); + } + + /// + /// These functions support parsing serialized RDF into an XMP object, and serailizing an XMP + /// object into RDF. + /// + /// + /// These functions support parsing serialized RDF into an XMP object, and serailizing an XMP + /// object into RDF. The input for parsing may be any valid Unicode + /// encoding. ISO Latin-1 is also recognized, but its use is strongly discouraged. Serialization + /// is always as UTF-8. + ///

+ /// parseFromBuffer() parses RDF from an InputStream. The encoding + /// is recognized automatically. + /// + /// an InputStream + /// + /// Options controlling the parsing.
+ /// The available options are: + ///

    + ///
  • XMP_REQUIRE_XMPMETA - The <x:xmpmeta> XML element is required around + /// <rdf:RDF>. + ///
  • XMP_STRICT_ALIASING - Do not reconcile alias differences, throw an exception. + ///
+ /// Note:The XMP_STRICT_ALIASING option is not yet implemented. + /// + /// Returns the XMPMeta-object created from the input. + /// If the file is not well-formed XML or if the parsing fails. + /// + public static XMPMeta Parse(InputStream @in, ParseOptions options) + { + return XMPMetaParser.Parse(@in, options); + } + + /// Parsing with default options. + /// + /// a String contain an XMP-file. + /// Returns the XMPMeta-object created from the input. + /// If the file is not well-formed XML or if the parsing fails. + /// + public static XMPMeta ParseFromString(string packet) + { + return ParseFromString(packet, null); + } + + /// Creates an XMPMeta-object from a string. + /// + /// a String contain an XMP-file. + /// Options controlling the parsing. + /// Returns the XMPMeta-object created from the input. + /// If the file is not well-formed XML or if the parsing fails. + /// + public static XMPMeta ParseFromString(string packet, ParseOptions options) + { + return XMPMetaParser.Parse(packet, options); + } + + /// Parsing with default options. + /// + /// a String contain an XMP-file. + /// Returns the XMPMeta-object created from the input. + /// If the file is not well-formed XML or if the parsing fails. + /// + public static XMPMeta ParseFromBuffer(sbyte[] buffer) + { + return ParseFromBuffer(buffer, null); + } + + /// Creates an XMPMeta-object from a byte-buffer. + /// + /// a String contain an XMP-file. + /// Options controlling the parsing. + /// Returns the XMPMeta-object created from the input. + /// If the file is not well-formed XML or if the parsing fails. + /// + public static XMPMeta ParseFromBuffer(sbyte[] buffer, ParseOptions options) + { + return XMPMetaParser.Parse(buffer, options); + } + + /// + /// Serializes an XMPMeta-object as RDF into an OutputStream + /// with default options. + /// + /// a metadata object + /// an OutputStream to write the serialized RDF to. + /// on serializsation errors. + /// + public static void Serialize(XMPMeta xmp, OutputStream @out) + { + Serialize(xmp, @out, null); + } + + /// Serializes an XMPMeta-object as RDF into an OutputStream. + /// a metadata object + /// + /// Options to control the serialization (see + /// + /// ). + /// + /// an OutputStream to write the serialized RDF to. + /// on serializsation errors. + /// + public static void Serialize(XMPMeta xmp, OutputStream @out, SerializeOptions options) + { + AssertImplementation(xmp); + XMPSerializerHelper.Serialize((XMPMetaImpl)xmp, @out, options); + } + + /// Serializes an XMPMeta-object as RDF into a byte buffer. + /// a metadata object + /// + /// Options to control the serialization (see + /// + /// ). + /// + /// Returns a byte buffer containing the serialized RDF. + /// on serializsation errors. + /// + public static sbyte[] SerializeToBuffer(XMPMeta xmp, SerializeOptions options) + { + AssertImplementation(xmp); + return XMPSerializerHelper.SerializeToBuffer((XMPMetaImpl)xmp, options); + } + + /// Serializes an XMPMeta-object as RDF into a string. + /// + /// Serializes an XMPMeta-object as RDF into a string. Note: Encoding + /// is ignored when serializing to a string. + /// + /// a metadata object + /// + /// Options to control the serialization (see + /// + /// ). + /// + /// Returns a string containing the serialized RDF. + /// on serializsation errors. + /// + public static string SerializeToString(XMPMeta xmp, SerializeOptions options) + { + AssertImplementation(xmp); + return XMPSerializerHelper.SerializeToString((XMPMetaImpl)xmp, options); + } + + /// Asserts that xmp is compatible to XMPMetaImpl.s + private static void AssertImplementation(XMPMeta xmp) + { + if (!(xmp is XMPMetaImpl)) + { + throw new NotSupportedException("The serializing service works only" + "with the XMPMeta implementation of this library"); + } + } + + /// Resets the schema registry to its original state (creates a new one). + /// + /// Resets the schema registry to its original state (creates a new one). + /// Be careful this might break all existing XMPMeta-objects and should be used + /// only for testing purpurses. + /// + public static void Reset() + { + schema = new XMPSchemaRegistryImpl(); + } + + /// Obtain version information. + /// + /// Obtain version information. The XMPVersionInfo singleton is created the first time + /// its requested. + /// + /// Returns the version information. + public static XMPVersionInfo GetVersionInfo() + { + lock (typeof(XMPMetaFactory)) + { + if (versionInfo == null) + { + try + { + int major = 5; + int minor = 1; + int micro = 0; + int engBuild = 3; + bool debug = false; + // Adobe XMP Core 5.0-jc001 DEBUG-., 2009 Jan 28 15:22:38-CET + string message = "Adobe XMP Core 5.1.0-jc003"; + versionInfo = new _XMPVersionInfo_274(major, minor, micro, debug, engBuild, message); + } + catch (Exception e) + { + // EMTPY, severe error would be detected during the tests + Console.Out.Println(e); + } + } + return versionInfo; + } + } + + private sealed class _XMPVersionInfo_274 : XMPVersionInfo + { + public _XMPVersionInfo_274(int major, int minor, int micro, bool debug, int engBuild, string message) + { + this.major = major; + this.minor = minor; + this.micro = micro; + this.debug = debug; + this.engBuild = engBuild; + this.message = message; + } + + public int GetMajor() + { + return major; + } + + public int GetMinor() + { + return minor; + } + + public int GetMicro() + { + return micro; + } + + public bool IsDebug() + { + return debug; + } + + public int GetBuild() + { + return engBuild; + } + + public string GetMessage() + { + return message; + } + + public override string ToString() + { + return message; + } + + private readonly int major; + + private readonly int minor; + + private readonly int micro; + + private readonly bool debug; + + private readonly int engBuild; + + private readonly string message; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPPathFactory.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPPathFactory.cs index e0d3c5752..146aec4ad 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPPathFactory.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPPathFactory.cs @@ -1,286 +1,286 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using Com.Adobe.Xmp.Impl; -using Com.Adobe.Xmp.Impl.Xpath; - -namespace Com.Adobe.Xmp -{ - /// Utility services for the metadata object. - /// - /// Utility services for the metadata object. It has only public static functions, you cannot create - /// an object. These are all functions that layer cleanly on top of the core XMP toolkit. - ///

- /// These functions provide support for composing path expressions to deeply nested properties. The - /// functions XMPMeta such as getProperty(), - /// getArrayItem() and getStructField() provide easy access to top - /// level simple properties, items in top level arrays, and fields of top level structs. They do not - /// provide convenient access to more complex things like fields several levels deep in a complex - /// struct, or fields within an array of structs, or items of an array that is a field of a struct. - /// These functions can also be used to compose paths to top level array items or struct fields so - /// that you can use the binary accessors like getPropertyAsInteger(). - ///

- /// You can use these functions is to compose a complete path expression, or all but the last - /// component. Suppose you have a property that is an array of integers within a struct. You can - /// access one of the array items like this: - ///

- ///

- ///
-    /// String path = XMPPathFactory.composeStructFieldPath (schemaNS, "Struct", fieldNS,
-    /// "Array");
-    /// String path += XMPPathFactory.composeArrayItemPath (schemaNS, "Array" index);
-    /// PropertyInteger result = xmpObj.getPropertyAsInteger(schemaNS, path);
-    /// 
- ///
You could also use this code if you want the string form of the integer: - ///
- ///
-    /// String path = XMPPathFactory.composeStructFieldPath (schemaNS, "Struct", fieldNS,
-    /// "Array");
-    /// PropertyText xmpObj.getArrayItem (schemaNS, path, index);
-    /// 
- ///
- ///

- /// Note: It might look confusing that the schemaNS is passed in all of the calls above. - /// This is because the XMP toolkit keeps the top level "schema" namespace separate from - /// the rest of the path expression. - /// Note: These methods are much simpler than in the C++-API, they don't check the given - /// path or array indices. - /// - /// 25.01.2006 - public sealed class XMPPathFactory - { - ///

Private constructor - private XMPPathFactory() - { - } - - // EMPTY - /// Compose the path expression for an item in an array. - /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. - /// - /// - /// The index of the desired item. Arrays in XMP are indexed from 1. - /// 0 and below means last array item and renders as [last()]. - /// - /// - /// Returns the composed path basing on fullPath. This will be of the form - /// ns:arrayName[i], where "ns" is the prefix for schemaNS and - /// "i" is the decimal representation of itemIndex. - /// - /// Throws exeption if index zero is used. - /// - public static string ComposeArrayItemPath(string arrayName, int itemIndex) - { - if (itemIndex > 0) - { - return arrayName + '[' + itemIndex + ']'; - } - else - { - if (itemIndex == XMPConstConstants.ArrayLastItem) - { - return arrayName + "[last()]"; - } - else - { - throw new XMPException("Array index must be larger than zero", XMPErrorConstants.Badindex); - } - } - } - - /// Compose the path expression for a field in a struct. - /// - /// Compose the path expression for a field in a struct. The result can be added to the - /// path of - /// - /// - /// The namespace URI for the field. Must not be null or the empty - /// string. - /// - /// - /// The name of the field. Must be a simple XML name, must not be - /// null or the empty string. - /// - /// - /// Returns the composed path. This will be of the form - /// ns:structName/fNS:fieldName, where "ns" is the prefix for - /// schemaNS and "fNS" is the prefix for fieldNS. - /// - /// Thrown if the path to create is not valid. - /// - public static string ComposeStructFieldPath(string fieldNS, string fieldName) - { - AssertFieldNS(fieldNS); - AssertFieldName(fieldName); - XMPPath fieldPath = XMPPathParser.ExpandXPath(fieldNS, fieldName); - if (fieldPath.Size() != 2) - { - throw new XMPException("The field name must be simple", XMPErrorConstants.Badxpath); - } - return '/' + fieldPath.GetSegment(XMPPath.StepRootProp).GetName(); - } - - /// Compose the path expression for a qualifier. - /// - /// The namespace URI for the qualifier. May be null or the empty - /// string if the qualifier is in the XML empty namespace. - /// - /// - /// The name of the qualifier. Must be a simple XML name, must not be - /// null or the empty string. - /// - /// - /// Returns the composed path. This will be of the form - /// ns:propName/?qNS:qualName, where "ns" is the prefix for - /// schemaNS and "qNS" is the prefix for qualNS. - /// - /// Thrown if the path to create is not valid. - /// - public static string ComposeQualifierPath(string qualNS, string qualName) - { - AssertQualNS(qualNS); - AssertQualName(qualName); - XMPPath qualPath = XMPPathParser.ExpandXPath(qualNS, qualName); - if (qualPath.Size() != 2) - { - throw new XMPException("The qualifier name must be simple", XMPErrorConstants.Badxpath); - } - return "/?" + qualPath.GetSegment(XMPPath.StepRootProp).GetName(); - } - - /// Compose the path expression to select an alternate item by language. - /// - /// Compose the path expression to select an alternate item by language. The - /// path syntax allows two forms of "content addressing" that may - /// be used to select an item in an array of alternatives. The form used in - /// ComposeLangSelector lets you select an item in an alt-text array based on - /// the value of its xml:lang qualifier. The other form of content - /// addressing is shown in ComposeFieldSelector. \note ComposeLangSelector - /// does not supplant SetLocalizedText or GetLocalizedText. They should - /// generally be used, as they provide extra logic to choose the appropriate - /// language and maintain consistency with the 'x-default' value. - /// ComposeLangSelector gives you an path expression that is explicitly and - /// only for the language given in the langName parameter. - /// - /// - /// The name of the array. May be a general path expression, must - /// not be null or the empty string. - /// - /// The RFC 3066 code for the desired language. - /// - /// Returns the composed path. This will be of the form - /// ns:arrayName[@xml:lang='langName'], where - /// "ns" is the prefix for schemaNS. - /// - public static string ComposeLangSelector(string arrayName, string langName) - { - return arrayName + "[?xml:lang=\"" + Utils.NormalizeLangValue(langName) + "\"]"; - } - - /// Compose the path expression to select an alternate item by a field's value. - /// - /// Compose the path expression to select an alternate item by a field's value. The path syntax - /// allows two forms of "content addressing" that may be used to select an item in an - /// array of alternatives. The form used in ComposeFieldSelector lets you select an item in an - /// array of structs based on the value of one of the fields in the structs. The other form of - /// content addressing is shown in ComposeLangSelector. For example, consider a simple struct - /// that has two fields, the name of a city and the URI of an FTP site in that city. Use this to - /// create an array of download alternatives. You can show the user a popup built from the values - /// of the city fields. You can then get the corresponding URI as follows: - ///

- ///

- ///
-        /// String path = composeFieldSelector ( schemaNS, "Downloads", fieldNS,
-        /// "City", chosenCity );
-        /// XMPProperty prop = xmpObj.getStructField ( schemaNS, path, fieldNS, "URI" );
-        /// 
- ///
- ///
- /// - /// The name of the array. May be a general path expression, must not be - /// null or the empty string. - /// - /// - /// The namespace URI for the field used as the selector. Must not be - /// null or the empty string. - /// - /// - /// The name of the field used as the selector. Must be a simple XML name, must - /// not be null or the empty string. It must be the name of a field that is - /// itself simple. - /// - /// The desired value of the field. - /// - /// Returns the composed path. This will be of the form - /// ns:arrayName[fNS:fieldName='fieldValue'], where "ns" is the - /// prefix for schemaNS and "fNS" is the prefix for fieldNS. - /// - /// Thrown if the path to create is not valid. - /// - public static string ComposeFieldSelector(string arrayName, string fieldNS, string fieldName, string fieldValue) - { - XMPPath fieldPath = XMPPathParser.ExpandXPath(fieldNS, fieldName); - if (fieldPath.Size() != 2) - { - throw new XMPException("The fieldName name must be simple", XMPErrorConstants.Badxpath); - } - return arrayName + '[' + fieldPath.GetSegment(XMPPath.StepRootProp).GetName() + "=\"" + fieldValue + "\"]"; - } - - /// ParameterAsserts that a qualifier namespace is set. - /// a qualifier namespace - /// Qualifier schema is null or empty - /// - private static void AssertQualNS(string qualNS) - { - if (qualNS == null || qualNS.Length == 0) - { - throw new XMPException("Empty qualifier namespace URI", XMPErrorConstants.Badschema); - } - } - - /// ParameterAsserts that a qualifier name is set. - /// a qualifier name or path - /// Qualifier name is null or empty - /// - private static void AssertQualName(string qualName) - { - if (qualName == null || qualName.Length == 0) - { - throw new XMPException("Empty qualifier name", XMPErrorConstants.Badxpath); - } - } - - /// ParameterAsserts that a struct field namespace is set. - /// a struct field namespace - /// Struct field schema is null or empty - /// - private static void AssertFieldNS(string fieldNS) - { - if (fieldNS == null || fieldNS.Length == 0) - { - throw new XMPException("Empty field namespace URI", XMPErrorConstants.Badschema); - } - } - - /// ParameterAsserts that a struct field name is set. - /// a struct field name or path - /// Struct field name is null or empty - /// - private static void AssertFieldName(string fieldName) - { - if (fieldName == null || fieldName.Length == 0) - { - throw new XMPException("Empty f name", XMPErrorConstants.Badxpath); - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using Com.Adobe.Xmp.Impl; +using Com.Adobe.Xmp.Impl.Xpath; + +namespace Com.Adobe.Xmp +{ + /// Utility services for the metadata object. + /// + /// Utility services for the metadata object. It has only public static functions, you cannot create + /// an object. These are all functions that layer cleanly on top of the core XMP toolkit. + ///

+ /// These functions provide support for composing path expressions to deeply nested properties. The + /// functions XMPMeta such as getProperty(), + /// getArrayItem() and getStructField() provide easy access to top + /// level simple properties, items in top level arrays, and fields of top level structs. They do not + /// provide convenient access to more complex things like fields several levels deep in a complex + /// struct, or fields within an array of structs, or items of an array that is a field of a struct. + /// These functions can also be used to compose paths to top level array items or struct fields so + /// that you can use the binary accessors like getPropertyAsInteger(). + ///

+ /// You can use these functions is to compose a complete path expression, or all but the last + /// component. Suppose you have a property that is an array of integers within a struct. You can + /// access one of the array items like this: + ///

+ ///

+ ///
+    /// String path = XMPPathFactory.composeStructFieldPath (schemaNS, "Struct", fieldNS,
+    /// "Array");
+    /// String path += XMPPathFactory.composeArrayItemPath (schemaNS, "Array" index);
+    /// PropertyInteger result = xmpObj.getPropertyAsInteger(schemaNS, path);
+    /// 
+ ///
You could also use this code if you want the string form of the integer: + ///
+ ///
+    /// String path = XMPPathFactory.composeStructFieldPath (schemaNS, "Struct", fieldNS,
+    /// "Array");
+    /// PropertyText xmpObj.getArrayItem (schemaNS, path, index);
+    /// 
+ ///
+ ///

+ /// Note: It might look confusing that the schemaNS is passed in all of the calls above. + /// This is because the XMP toolkit keeps the top level "schema" namespace separate from + /// the rest of the path expression. + /// Note: These methods are much simpler than in the C++-API, they don't check the given + /// path or array indices. + /// + /// 25.01.2006 + public sealed class XMPPathFactory + { + ///

Private constructor + private XMPPathFactory() + { + } + + // EMPTY + /// Compose the path expression for an item in an array. + /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. + /// + /// + /// The index of the desired item. Arrays in XMP are indexed from 1. + /// 0 and below means last array item and renders as [last()]. + /// + /// + /// Returns the composed path basing on fullPath. This will be of the form + /// ns:arrayName[i], where "ns" is the prefix for schemaNS and + /// "i" is the decimal representation of itemIndex. + /// + /// Throws exeption if index zero is used. + /// + public static string ComposeArrayItemPath(string arrayName, int itemIndex) + { + if (itemIndex > 0) + { + return arrayName + '[' + itemIndex + ']'; + } + else + { + if (itemIndex == XMPConstConstants.ArrayLastItem) + { + return arrayName + "[last()]"; + } + else + { + throw new XMPException("Array index must be larger than zero", XMPErrorConstants.Badindex); + } + } + } + + /// Compose the path expression for a field in a struct. + /// + /// Compose the path expression for a field in a struct. The result can be added to the + /// path of + /// + /// + /// The namespace URI for the field. Must not be null or the empty + /// string. + /// + /// + /// The name of the field. Must be a simple XML name, must not be + /// null or the empty string. + /// + /// + /// Returns the composed path. This will be of the form + /// ns:structName/fNS:fieldName, where "ns" is the prefix for + /// schemaNS and "fNS" is the prefix for fieldNS. + /// + /// Thrown if the path to create is not valid. + /// + public static string ComposeStructFieldPath(string fieldNS, string fieldName) + { + AssertFieldNS(fieldNS); + AssertFieldName(fieldName); + XMPPath fieldPath = XMPPathParser.ExpandXPath(fieldNS, fieldName); + if (fieldPath.Size() != 2) + { + throw new XMPException("The field name must be simple", XMPErrorConstants.Badxpath); + } + return '/' + fieldPath.GetSegment(XMPPath.StepRootProp).GetName(); + } + + /// Compose the path expression for a qualifier. + /// + /// The namespace URI for the qualifier. May be null or the empty + /// string if the qualifier is in the XML empty namespace. + /// + /// + /// The name of the qualifier. Must be a simple XML name, must not be + /// null or the empty string. + /// + /// + /// Returns the composed path. This will be of the form + /// ns:propName/?qNS:qualName, where "ns" is the prefix for + /// schemaNS and "qNS" is the prefix for qualNS. + /// + /// Thrown if the path to create is not valid. + /// + public static string ComposeQualifierPath(string qualNS, string qualName) + { + AssertQualNS(qualNS); + AssertQualName(qualName); + XMPPath qualPath = XMPPathParser.ExpandXPath(qualNS, qualName); + if (qualPath.Size() != 2) + { + throw new XMPException("The qualifier name must be simple", XMPErrorConstants.Badxpath); + } + return "/?" + qualPath.GetSegment(XMPPath.StepRootProp).GetName(); + } + + /// Compose the path expression to select an alternate item by language. + /// + /// Compose the path expression to select an alternate item by language. The + /// path syntax allows two forms of "content addressing" that may + /// be used to select an item in an array of alternatives. The form used in + /// ComposeLangSelector lets you select an item in an alt-text array based on + /// the value of its xml:lang qualifier. The other form of content + /// addressing is shown in ComposeFieldSelector. \note ComposeLangSelector + /// does not supplant SetLocalizedText or GetLocalizedText. They should + /// generally be used, as they provide extra logic to choose the appropriate + /// language and maintain consistency with the 'x-default' value. + /// ComposeLangSelector gives you an path expression that is explicitly and + /// only for the language given in the langName parameter. + /// + /// + /// The name of the array. May be a general path expression, must + /// not be null or the empty string. + /// + /// The RFC 3066 code for the desired language. + /// + /// Returns the composed path. This will be of the form + /// ns:arrayName[@xml:lang='langName'], where + /// "ns" is the prefix for schemaNS. + /// + public static string ComposeLangSelector(string arrayName, string langName) + { + return arrayName + "[?xml:lang=\"" + Utils.NormalizeLangValue(langName) + "\"]"; + } + + /// Compose the path expression to select an alternate item by a field's value. + /// + /// Compose the path expression to select an alternate item by a field's value. The path syntax + /// allows two forms of "content addressing" that may be used to select an item in an + /// array of alternatives. The form used in ComposeFieldSelector lets you select an item in an + /// array of structs based on the value of one of the fields in the structs. The other form of + /// content addressing is shown in ComposeLangSelector. For example, consider a simple struct + /// that has two fields, the name of a city and the URI of an FTP site in that city. Use this to + /// create an array of download alternatives. You can show the user a popup built from the values + /// of the city fields. You can then get the corresponding URI as follows: + ///

+ ///

+ ///
+        /// String path = composeFieldSelector ( schemaNS, "Downloads", fieldNS,
+        /// "City", chosenCity );
+        /// XMPProperty prop = xmpObj.getStructField ( schemaNS, path, fieldNS, "URI" );
+        /// 
+ ///
+ ///
+ /// + /// The name of the array. May be a general path expression, must not be + /// null or the empty string. + /// + /// + /// The namespace URI for the field used as the selector. Must not be + /// null or the empty string. + /// + /// + /// The name of the field used as the selector. Must be a simple XML name, must + /// not be null or the empty string. It must be the name of a field that is + /// itself simple. + /// + /// The desired value of the field. + /// + /// Returns the composed path. This will be of the form + /// ns:arrayName[fNS:fieldName='fieldValue'], where "ns" is the + /// prefix for schemaNS and "fNS" is the prefix for fieldNS. + /// + /// Thrown if the path to create is not valid. + /// + public static string ComposeFieldSelector(string arrayName, string fieldNS, string fieldName, string fieldValue) + { + XMPPath fieldPath = XMPPathParser.ExpandXPath(fieldNS, fieldName); + if (fieldPath.Size() != 2) + { + throw new XMPException("The fieldName name must be simple", XMPErrorConstants.Badxpath); + } + return arrayName + '[' + fieldPath.GetSegment(XMPPath.StepRootProp).GetName() + "=\"" + fieldValue + "\"]"; + } + + /// ParameterAsserts that a qualifier namespace is set. + /// a qualifier namespace + /// Qualifier schema is null or empty + /// + private static void AssertQualNS(string qualNS) + { + if (qualNS == null || qualNS.Length == 0) + { + throw new XMPException("Empty qualifier namespace URI", XMPErrorConstants.Badschema); + } + } + + /// ParameterAsserts that a qualifier name is set. + /// a qualifier name or path + /// Qualifier name is null or empty + /// + private static void AssertQualName(string qualName) + { + if (qualName == null || qualName.Length == 0) + { + throw new XMPException("Empty qualifier name", XMPErrorConstants.Badxpath); + } + } + + /// ParameterAsserts that a struct field namespace is set. + /// a struct field namespace + /// Struct field schema is null or empty + /// + private static void AssertFieldNS(string fieldNS) + { + if (fieldNS == null || fieldNS.Length == 0) + { + throw new XMPException("Empty field namespace URI", XMPErrorConstants.Badschema); + } + } + + /// ParameterAsserts that a struct field name is set. + /// a struct field name or path + /// Struct field name is null or empty + /// + private static void AssertFieldName(string fieldName) + { + if (fieldName == null || fieldName.Length == 0) + { + throw new XMPException("Empty f name", XMPErrorConstants.Badxpath); + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPSchemaRegistry.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPSchemaRegistry.cs index b86beafd2..a455b1b94 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPSchemaRegistry.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPSchemaRegistry.cs @@ -1,165 +1,165 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Collections; -using Com.Adobe.Xmp.Properties; - -namespace Com.Adobe.Xmp -{ - /// - /// The schema registry keeps track of all namespaces and aliases used in the XMP - /// metadata. - /// - /// - /// The schema registry keeps track of all namespaces and aliases used in the XMP - /// metadata. At initialisation time, the default namespaces and default aliases - /// are automatically registered. Namespaces must be registered before - /// used in namespace URI parameters or path expressions. Within the XMP Toolkit - /// the registered namespace URIs and prefixes must be unique. Additional - /// namespaces encountered when parsing RDF are automatically registered. The - /// namespace URI should always end in an XML name separator such as '/' or '#'. - /// This is because some forms of RDF shorthand catenate a namespace URI with an - /// element name to form a new URI. - ///

- /// Aliases in XMP serve the same purpose as Windows file shortcuts, - /// Macintosh file aliases, or UNIX file symbolic links. The aliases are simply - /// multiple names for the same property. One distinction of XMP aliases is that - /// they are ordered, there is an alias name pointing to an actual name. The - /// primary significance of the actual name is that it is the preferred name for - /// output, generally the most widely recognized name. - ///

- /// The names that can be aliased in XMP are restricted. The alias must be a top - /// level property name, not a field within a structure or an element within an - /// array. The actual may be a top level property name, the first element within - /// a top level array, or the default element in an alt-text array. This does not - /// mean the alias can only be a simple property. It is OK to alias a top level - /// structure or array to an identical top level structure or array, or to the - /// first item of an array of structures. - /// - /// 27.01.2006 - public interface XMPSchemaRegistry - { - // --------------------------------------------------------------------------------------------- - // Namespace Functions - ///

Register a namespace URI with a suggested prefix. - /// - /// Register a namespace URI with a suggested prefix. It is not an error if - /// the URI is already registered, no matter what the prefix is. If the URI - /// is not registered but the suggested prefix is in use, a unique prefix is - /// created from the suggested one. The actual registeed prefix is always - /// returned. The function result tells if the registered prefix is the - /// suggested one. - ///

- /// Note: No checking is presently done on either the URI or the prefix. - /// - /// The URI for the namespace. Must be a valid XML URI. - /// - /// The suggested prefix to be used if the URI is not yet - /// registered. Must be a valid XML name. - /// - /// - /// Returns the registered prefix for this URI, is equal to the - /// suggestedPrefix if the namespace hasn't been registered before, - /// otherwise the existing prefix. - /// - /// If the parameters are not accordingly set - /// - string RegisterNamespace(string namespaceURI, string suggestedPrefix); - - ///

Obtain the prefix for a registered namespace URI. - /// - /// Obtain the prefix for a registered namespace URI. - ///

- /// It is not an error if the namespace URI is not registered. - /// - /// - /// The URI for the namespace. Must not be null or the empty - /// string. - /// - /// Returns the prefix registered for this namespace URI or null. - string GetNamespacePrefix(string namespaceURI); - - ///

Obtain the URI for a registered namespace prefix. - /// - /// Obtain the URI for a registered namespace prefix. - ///

- /// It is not an error if the namespace prefix is not registered. - /// - /// - /// The prefix for the namespace. Must not be null or the empty - /// string. - /// - /// Returns the URI registered for this prefix or null. - string GetNamespaceURI(string namespacePrefix); - - /// - /// Returns the registered prefix/namespace-pairs as map, where the keys are the - /// namespaces and the values are the prefixes. - /// - IDictionary GetNamespaces(); - - /// - /// Returns the registered namespace/prefix-pairs as map, where the keys are the - /// prefixes and the values are the namespaces. - /// - IDictionary GetPrefixes(); - - ///

Deletes a namespace from the registry. - /// - /// Deletes a namespace from the registry. - ///

- /// Does nothing if the URI is not registered, or if the namespaceURI - /// parameter is null or the empty string. - ///

- /// Note: Not yet implemented. - /// - /// The URI for the namespace. - void DeleteNamespace(string namespaceURI); - - // --------------------------------------------------------------------------------------------- - // Alias Functions - ///

Determines if a name is an alias, and what it is aliased to. - /// - /// The namespace URI of the alias. Must not be null or the empty - /// string. - /// - /// - /// The name of the alias. May be an arbitrary path expression - /// path, must not be null or the empty string. - /// - /// - /// Returns the XMPAliasInfo for the given alias namespace and property or - /// null if there is no such alias. - /// - XMPAliasInfo ResolveAlias(string aliasNS, string aliasProp); - - /// Collects all aliases that are contained in the provided namespace. - /// - /// Collects all aliases that are contained in the provided namespace. - /// If nothing is found, an empty array is returned. - /// - /// a schema namespace URI - /// Returns all alias infos from aliases that are contained in the provided namespace. - XMPAliasInfo[] FindAliases(string aliasNS); - - /// Searches for registered aliases. - /// an XML conform qname - /// - /// Returns if an alias definition for the given qname to another - /// schema and property is registered. - /// - XMPAliasInfo FindAlias(string qname); - - /// - /// Returns the registered aliases as map, where the key is the "qname" (prefix and name) - /// and the value an XMPAliasInfo-object. - /// - IDictionary GetAliases(); - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Collections; +using Com.Adobe.Xmp.Properties; + +namespace Com.Adobe.Xmp +{ + /// + /// The schema registry keeps track of all namespaces and aliases used in the XMP + /// metadata. + /// + /// + /// The schema registry keeps track of all namespaces and aliases used in the XMP + /// metadata. At initialisation time, the default namespaces and default aliases + /// are automatically registered. Namespaces must be registered before + /// used in namespace URI parameters or path expressions. Within the XMP Toolkit + /// the registered namespace URIs and prefixes must be unique. Additional + /// namespaces encountered when parsing RDF are automatically registered. The + /// namespace URI should always end in an XML name separator such as '/' or '#'. + /// This is because some forms of RDF shorthand catenate a namespace URI with an + /// element name to form a new URI. + ///

+ /// Aliases in XMP serve the same purpose as Windows file shortcuts, + /// Macintosh file aliases, or UNIX file symbolic links. The aliases are simply + /// multiple names for the same property. One distinction of XMP aliases is that + /// they are ordered, there is an alias name pointing to an actual name. The + /// primary significance of the actual name is that it is the preferred name for + /// output, generally the most widely recognized name. + ///

+ /// The names that can be aliased in XMP are restricted. The alias must be a top + /// level property name, not a field within a structure or an element within an + /// array. The actual may be a top level property name, the first element within + /// a top level array, or the default element in an alt-text array. This does not + /// mean the alias can only be a simple property. It is OK to alias a top level + /// structure or array to an identical top level structure or array, or to the + /// first item of an array of structures. + /// + /// 27.01.2006 + public interface XMPSchemaRegistry + { + // --------------------------------------------------------------------------------------------- + // Namespace Functions + ///

Register a namespace URI with a suggested prefix. + /// + /// Register a namespace URI with a suggested prefix. It is not an error if + /// the URI is already registered, no matter what the prefix is. If the URI + /// is not registered but the suggested prefix is in use, a unique prefix is + /// created from the suggested one. The actual registeed prefix is always + /// returned. The function result tells if the registered prefix is the + /// suggested one. + ///

+ /// Note: No checking is presently done on either the URI or the prefix. + /// + /// The URI for the namespace. Must be a valid XML URI. + /// + /// The suggested prefix to be used if the URI is not yet + /// registered. Must be a valid XML name. + /// + /// + /// Returns the registered prefix for this URI, is equal to the + /// suggestedPrefix if the namespace hasn't been registered before, + /// otherwise the existing prefix. + /// + /// If the parameters are not accordingly set + /// + string RegisterNamespace(string namespaceURI, string suggestedPrefix); + + ///

Obtain the prefix for a registered namespace URI. + /// + /// Obtain the prefix for a registered namespace URI. + ///

+ /// It is not an error if the namespace URI is not registered. + /// + /// + /// The URI for the namespace. Must not be null or the empty + /// string. + /// + /// Returns the prefix registered for this namespace URI or null. + string GetNamespacePrefix(string namespaceURI); + + ///

Obtain the URI for a registered namespace prefix. + /// + /// Obtain the URI for a registered namespace prefix. + ///

+ /// It is not an error if the namespace prefix is not registered. + /// + /// + /// The prefix for the namespace. Must not be null or the empty + /// string. + /// + /// Returns the URI registered for this prefix or null. + string GetNamespaceURI(string namespacePrefix); + + /// + /// Returns the registered prefix/namespace-pairs as map, where the keys are the + /// namespaces and the values are the prefixes. + /// + IDictionary GetNamespaces(); + + /// + /// Returns the registered namespace/prefix-pairs as map, where the keys are the + /// prefixes and the values are the namespaces. + /// + IDictionary GetPrefixes(); + + ///

Deletes a namespace from the registry. + /// + /// Deletes a namespace from the registry. + ///

+ /// Does nothing if the URI is not registered, or if the namespaceURI + /// parameter is null or the empty string. + ///

+ /// Note: Not yet implemented. + /// + /// The URI for the namespace. + void DeleteNamespace(string namespaceURI); + + // --------------------------------------------------------------------------------------------- + // Alias Functions + ///

Determines if a name is an alias, and what it is aliased to. + /// + /// The namespace URI of the alias. Must not be null or the empty + /// string. + /// + /// + /// The name of the alias. May be an arbitrary path expression + /// path, must not be null or the empty string. + /// + /// + /// Returns the XMPAliasInfo for the given alias namespace and property or + /// null if there is no such alias. + /// + XMPAliasInfo ResolveAlias(string aliasNS, string aliasProp); + + /// Collects all aliases that are contained in the provided namespace. + /// + /// Collects all aliases that are contained in the provided namespace. + /// If nothing is found, an empty array is returned. + /// + /// a schema namespace URI + /// Returns all alias infos from aliases that are contained in the provided namespace. + XMPAliasInfo[] FindAliases(string aliasNS); + + /// Searches for registered aliases. + /// an XML conform qname + /// + /// Returns if an alias definition for the given qname to another + /// schema and property is registered. + /// + XMPAliasInfo FindAlias(string qname); + + /// + /// Returns the registered aliases as map, where the key is the "qname" (prefix and name) + /// and the value an XMPAliasInfo-object. + /// + IDictionary GetAliases(); + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPUtils.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPUtils.cs index acd480939..bfdb4411e 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPUtils.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPUtils.cs @@ -1,433 +1,433 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using Com.Adobe.Xmp.Impl; -using Com.Adobe.Xmp.Options; -using Sharpen; - -namespace Com.Adobe.Xmp -{ - /// Utility methods for XMP. - /// - /// Utility methods for XMP. I included only those that are different from the - /// Java default conversion utilities. - /// - /// 21.02.2006 - public class XMPUtils - { - /// Private constructor - private XMPUtils() - { - } - - // EMPTY - /// Create a single edit string from an array of strings. - /// The XMP object containing the array to be catenated. - /// - /// The schema namespace URI for the array. Must not be null or - /// the empty string. - /// - /// - /// The name of the array. May be a general path expression, must - /// not be null or the empty string. Each item in the array must - /// be a simple string value. - /// - /// - /// The string to be used to separate the items in the catenated - /// string. Defaults to "; ", ASCII semicolon and space - /// (U+003B, U+0020). - /// - /// - /// The characters to be used as quotes around array items that - /// contain a separator. Defaults to '"' - /// - /// Option flag to control the catenation. - /// Returns the string containing the catenated array items. - /// Forwards the Exceptions from the metadata processing - /// - public static string CatenateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string separator, string quotes, bool allowCommas) - { - return XMPUtilsImpl.CatenateArrayItems(xmp, schemaNS, arrayName, separator, quotes, allowCommas); - } - - /// Separate a single edit string into an array of strings. - /// The XMP object containing the array to be updated. - /// - /// The schema namespace URI for the array. Must not be null or - /// the empty string. - /// - /// - /// The name of the array. May be a general path expression, must - /// not be null or the empty string. Each item in the array must - /// be a simple string value. - /// - /// The string to be separated into the array items. - /// Option flags to control the separation. - /// Flag if commas shall be preserved - /// Forwards the Exceptions from the metadata processing - /// - public static void SeparateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) - { - XMPUtilsImpl.SeparateArrayItems(xmp, schemaNS, arrayName, catedStr, arrayOptions, preserveCommas); - } - - /// Remove multiple properties from an XMP object. - /// - /// Remove multiple properties from an XMP object. - /// RemoveProperties was created to support the File Info dialog's Delete - /// button, and has been been generalized somewhat from those specific needs. - /// It operates in one of three main modes depending on the schemaNS and - /// propName parameters: - ///
    - ///
  • Non-empty schemaNS and propName - The named property is - /// removed if it is an external property, or if the - /// flag doAllProperties option is true. It does not matter whether the - /// named property is an actual property or an alias. - ///
  • Non-empty schemaNS and empty propName - The all external - /// properties in the named schema are removed. Internal properties are also - /// removed if the flag doAllProperties option is set. In addition, - /// aliases from the named schema will be removed if the flag includeAliases - /// option is set. - ///
  • Empty schemaNS and empty propName - All external properties in - /// all schema are removed. Internal properties are also removed if the - /// flag doAllProperties option is passed. Aliases are implicitly handled - /// because the associated actuals are internal if the alias is. - ///
- /// It is an error to pass an empty schemaNS and non-empty propName. - ///
- /// The XMP object containing the properties to be removed. - /// - /// Optional schema namespace URI for the properties to be - /// removed. - /// - /// Optional path expression for the property to be removed. - /// - /// Option flag to control the deletion: do internal properties in - /// addition to external properties. - /// - /// - /// Option flag to control the deletion: - /// Include aliases in the "named schema" case above. - /// Note: Currently not supported. - /// - /// Forwards the Exceptions from the metadata processing - /// - public static void RemoveProperties(XMPMeta xmp, string schemaNS, string propName, bool doAllProperties, bool includeAliases) - { - XMPUtilsImpl.RemoveProperties(xmp, schemaNS, propName, doAllProperties, includeAliases); - } - - /// Alias without the new option deleteEmptyValues. - /// The source XMP object. - /// The destination XMP object. - /// Do internal properties in addition to external properties. - /// Replace the values of existing properties. - /// Forwards the Exceptions from the metadata processing - /// - public static void AppendProperties(XMPMeta source, XMPMeta dest, bool doAllProperties, bool replaceOldValues) - { - AppendProperties(source, dest, doAllProperties, replaceOldValues, false); - } - - ///

Append properties from one XMP object to another.

- /// - ///

Append properties from one XMP object to another. - ///

XMPUtils#appendProperties was created to support the File Info dialog's Append button, and - /// has been been generalized somewhat from those specific needs. It appends information from one - /// XMP object (source) to another (dest). The default operation is to append only external - /// properties that do not already exist in the destination. The flag - /// doAllProperties can be used to operate on all properties, external and internal. - /// The flag replaceOldValues option can be used to replace the values - /// of existing properties. The notion of external - /// versus internal applies only to top level properties. The keep-or-replace-old notion applies - /// within structs and arrays as described below. - ///

    - ///
  • If replaceOldValues is true then the processing is restricted to the top - /// level properties. The processed properties from the source (according to - /// doAllProperties) are propagated to the destination, - /// replacing any existing values.Properties in the destination that are not in the source - /// are left alone. - ///
  • If replaceOldValues is not passed then the processing is more complicated. - /// Top level properties are added to the destination if they do not already exist. - /// If they do exist but differ in form (simple/struct/array) then the destination is left alone. - /// If the forms match, simple properties are left unchanged while structs and arrays are merged. - ///
  • If deleteEmptyValues is passed then an empty value in the source XMP causes - /// the corresponding destination XMP property to be deleted. The default is to treat empty - /// values the same as non-empty values. An empty value is any of a simple empty string, an array - /// with no items, or a struct with no fields. Qualifiers are ignored. - ///
- ///

The detailed behavior is defined by the following pseudo-code: - ///

- ///
-        /// appendProperties ( sourceXMP, destXMP, doAllProperties,
-        /// replaceOldValues, deleteEmptyValues ):
-        /// for all source schema (top level namespaces):
-        /// for all top level properties in sourceSchema:
-        /// if doAllProperties or prop is external:
-        /// appendSubtree ( sourceNode, destSchema, replaceOldValues, deleteEmptyValues )
-        /// appendSubtree ( sourceNode, destParent, replaceOldValues, deleteEmptyValues ):
-        /// if deleteEmptyValues and source value is empty:
-        /// delete the corresponding child from destParent
-        /// else if sourceNode not in destParent (by name):
-        /// copy sourceNode's subtree to destParent
-        /// else if replaceOld:
-        /// delete subtree from destParent
-        /// copy sourceNode's subtree to destParent
-        /// else:
-        /// // Already exists in dest and not replacing, merge structs and arrays
-        /// if sourceNode and destNode forms differ:
-        /// return, leave the destNode alone
-        /// else if form is a struct:
-        /// for each field in sourceNode:
-        /// AppendSubtree ( sourceNode.field, destNode, replaceOldValues )
-        /// else if form is an alt-text array:
-        /// copy new items by "xml:lang" value into the destination
-        /// else if form is an array:
-        /// copy new items by value into the destination, ignoring order and duplicates
-        /// 
- ///
- ///

Note: appendProperties can be expensive if replaceOldValues is not passed and - /// the XMP contains large arrays. The array item checking described above is n-squared. - /// Each source item is checked to see if it already exists in the destination, - /// without regard to order or duplicates. - ///

Simple items are compared by value and "xml:lang" qualifier, other qualifiers are ignored. - /// Structs are recursively compared by field names, without regard to field order. Arrays are - /// compared by recursively comparing all items. - /// - /// The source XMP object. - /// The destination XMP object. - /// Do internal properties in addition to external properties. - /// Replace the values of existing properties. - /// Delete destination values if source property is empty. - /// Forwards the Exceptions from the metadata processing - /// - public static void AppendProperties(XMPMeta source, XMPMeta dest, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) - { - XMPUtilsImpl.AppendProperties(source, dest, doAllProperties, replaceOldValues, deleteEmptyValues); - } - - ///

Convert from string to Boolean. - /// The string representation of the Boolean. - /// - /// The appropriate boolean value for the string. The checked values - /// for true and false are: - ///
    - ///
  • - /// - /// and - /// - ///
  • "t" and "f" - ///
  • "on" and "off" - ///
  • "yes" and "no" - ///
  • "value <> 0" and "value == 0" - ///
- ///
- /// If an empty string is passed. - /// - public static bool ConvertToBoolean(string value) - { - if (value == null || value.Length == 0) - { - throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); - } - value = value.ToLower(); - try - { - // First try interpretation as Integer (anything not 0 is true) - return Convert.ToInt32(value) != 0; - } - catch (FormatException) - { - return "true".Equals(value) || "t".Equals(value) || "on".Equals(value) || "yes".Equals(value); - } - } - - /// Convert from boolean to string. - /// a boolean value - /// - /// The XMP string representation of the boolean. The values used are - /// given by the constnts - /// - /// and - /// - /// . - /// - public static string ConvertFromBoolean(bool value) - { - return value ? XMPConstConstants.Truestr : XMPConstConstants.Falsestr; - } - - /// Converts a string value to an int. - /// the string value - /// Returns an int. - /// - /// If the rawValue is null or empty or the - /// conversion fails. - /// - /// - public static int ConvertToInteger(string rawValue) - { - try - { - if (rawValue == null || rawValue.Length == 0) - { - throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); - } - if (rawValue.StartsWith("0x")) - { - return Convert.ToInt32(Runtime.Substring(rawValue, 2), 16); - } - else - { - return Convert.ToInt32(rawValue); - } - } - catch (FormatException) - { - throw new XMPException("Invalid integer string", XMPErrorConstants.Badvalue); - } - } - - /// Convert from int to string. - /// an int value - /// The string representation of the int. - public static string ConvertFromInteger(int value) - { - return value.ToString(); - } - - /// Converts a string value to a long. - /// the string value - /// Returns a long. - /// - /// If the rawValue is null or empty or the - /// conversion fails. - /// - /// - public static long ConvertToLong(string rawValue) - { - try - { - if (rawValue == null || rawValue.Length == 0) - { - throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); - } - if (rawValue.StartsWith("0x")) - { - return Convert.ToInt64(Runtime.Substring(rawValue, 2), 16); - } - else - { - return Convert.ToInt64(rawValue); - } - } - catch (FormatException) - { - throw new XMPException("Invalid long string", XMPErrorConstants.Badvalue); - } - } - - /// Convert from long to string. - /// a long value - /// The string representation of the long. - public static string ConvertFromLong(long value) - { - return value.ToString(); - } - - /// Converts a string value to a double. - /// the string value - /// Returns a double. - /// - /// If the rawValue is null or empty or the - /// conversion fails. - /// - /// - public static double ConvertToDouble(string rawValue) - { - try - { - if (rawValue == null || rawValue.Length == 0) - { - throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); - } - else - { - return double.Parse(rawValue); - } - } - catch (FormatException) - { - throw new XMPException("Invalid double string", XMPErrorConstants.Badvalue); - } - } - - /// Convert from long to string. - /// a long value - /// The string representation of the long. - public static string ConvertFromDouble(double value) - { - return value.ToString(); - } - - /// Converts a string value to an XMPDateTime. - /// the string value - /// Returns an XMPDateTime-object. - /// - /// If the rawValue is null or empty or the - /// conversion fails. - /// - /// - public static XMPDateTime ConvertToDate(string rawValue) - { - if (rawValue == null || rawValue.Length == 0) - { - throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); - } - else - { - return ISO8601Converter.Parse(rawValue); - } - } - - /// Convert from XMPDateTime to string. - /// an XMPDateTime - /// The string representation of the long. - public static string ConvertFromDate(XMPDateTime value) - { - return ISO8601Converter.Render(value); - } - - /// Convert from a byte array to a base64 encoded string. - /// the byte array to be converted - /// Returns the base64 string. - public static string EncodeBase64(sbyte[] buffer) - { - return Runtime.GetStringForBytes(Base64.Encode(buffer)); - } - - /// Decode from Base64 encoded string to raw data. - /// a base64 encoded string - /// Returns a byte array containg the decoded string. - /// Thrown if the given string is not property base64 encoded - /// - public static sbyte[] DecodeBase64(string base64String) - { - try - { - return Base64.Decode(Runtime.GetBytesForString(base64String)); - } - catch (Exception e) - { - throw new XMPException("Invalid base64 string", XMPErrorConstants.Badvalue, e); - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using Com.Adobe.Xmp.Impl; +using Com.Adobe.Xmp.Options; +using Sharpen; + +namespace Com.Adobe.Xmp +{ + /// Utility methods for XMP. + /// + /// Utility methods for XMP. I included only those that are different from the + /// Java default conversion utilities. + /// + /// 21.02.2006 + public class XMPUtils + { + /// Private constructor + private XMPUtils() + { + } + + // EMPTY + /// Create a single edit string from an array of strings. + /// The XMP object containing the array to be catenated. + /// + /// The schema namespace URI for the array. Must not be null or + /// the empty string. + /// + /// + /// The name of the array. May be a general path expression, must + /// not be null or the empty string. Each item in the array must + /// be a simple string value. + /// + /// + /// The string to be used to separate the items in the catenated + /// string. Defaults to "; ", ASCII semicolon and space + /// (U+003B, U+0020). + /// + /// + /// The characters to be used as quotes around array items that + /// contain a separator. Defaults to '"' + /// + /// Option flag to control the catenation. + /// Returns the string containing the catenated array items. + /// Forwards the Exceptions from the metadata processing + /// + public static string CatenateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string separator, string quotes, bool allowCommas) + { + return XMPUtilsImpl.CatenateArrayItems(xmp, schemaNS, arrayName, separator, quotes, allowCommas); + } + + /// Separate a single edit string into an array of strings. + /// The XMP object containing the array to be updated. + /// + /// The schema namespace URI for the array. Must not be null or + /// the empty string. + /// + /// + /// The name of the array. May be a general path expression, must + /// not be null or the empty string. Each item in the array must + /// be a simple string value. + /// + /// The string to be separated into the array items. + /// Option flags to control the separation. + /// Flag if commas shall be preserved + /// Forwards the Exceptions from the metadata processing + /// + public static void SeparateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) + { + XMPUtilsImpl.SeparateArrayItems(xmp, schemaNS, arrayName, catedStr, arrayOptions, preserveCommas); + } + + /// Remove multiple properties from an XMP object. + /// + /// Remove multiple properties from an XMP object. + /// RemoveProperties was created to support the File Info dialog's Delete + /// button, and has been been generalized somewhat from those specific needs. + /// It operates in one of three main modes depending on the schemaNS and + /// propName parameters: + ///
    + ///
  • Non-empty schemaNS and propName - The named property is + /// removed if it is an external property, or if the + /// flag doAllProperties option is true. It does not matter whether the + /// named property is an actual property or an alias. + ///
  • Non-empty schemaNS and empty propName - The all external + /// properties in the named schema are removed. Internal properties are also + /// removed if the flag doAllProperties option is set. In addition, + /// aliases from the named schema will be removed if the flag includeAliases + /// option is set. + ///
  • Empty schemaNS and empty propName - All external properties in + /// all schema are removed. Internal properties are also removed if the + /// flag doAllProperties option is passed. Aliases are implicitly handled + /// because the associated actuals are internal if the alias is. + ///
+ /// It is an error to pass an empty schemaNS and non-empty propName. + ///
+ /// The XMP object containing the properties to be removed. + /// + /// Optional schema namespace URI for the properties to be + /// removed. + /// + /// Optional path expression for the property to be removed. + /// + /// Option flag to control the deletion: do internal properties in + /// addition to external properties. + /// + /// + /// Option flag to control the deletion: + /// Include aliases in the "named schema" case above. + /// Note: Currently not supported. + /// + /// Forwards the Exceptions from the metadata processing + /// + public static void RemoveProperties(XMPMeta xmp, string schemaNS, string propName, bool doAllProperties, bool includeAliases) + { + XMPUtilsImpl.RemoveProperties(xmp, schemaNS, propName, doAllProperties, includeAliases); + } + + /// Alias without the new option deleteEmptyValues. + /// The source XMP object. + /// The destination XMP object. + /// Do internal properties in addition to external properties. + /// Replace the values of existing properties. + /// Forwards the Exceptions from the metadata processing + /// + public static void AppendProperties(XMPMeta source, XMPMeta dest, bool doAllProperties, bool replaceOldValues) + { + AppendProperties(source, dest, doAllProperties, replaceOldValues, false); + } + + ///

Append properties from one XMP object to another.

+ /// + ///

Append properties from one XMP object to another. + ///

XMPUtils#appendProperties was created to support the File Info dialog's Append button, and + /// has been been generalized somewhat from those specific needs. It appends information from one + /// XMP object (source) to another (dest). The default operation is to append only external + /// properties that do not already exist in the destination. The flag + /// doAllProperties can be used to operate on all properties, external and internal. + /// The flag replaceOldValues option can be used to replace the values + /// of existing properties. The notion of external + /// versus internal applies only to top level properties. The keep-or-replace-old notion applies + /// within structs and arrays as described below. + ///

    + ///
  • If replaceOldValues is true then the processing is restricted to the top + /// level properties. The processed properties from the source (according to + /// doAllProperties) are propagated to the destination, + /// replacing any existing values.Properties in the destination that are not in the source + /// are left alone. + ///
  • If replaceOldValues is not passed then the processing is more complicated. + /// Top level properties are added to the destination if they do not already exist. + /// If they do exist but differ in form (simple/struct/array) then the destination is left alone. + /// If the forms match, simple properties are left unchanged while structs and arrays are merged. + ///
  • If deleteEmptyValues is passed then an empty value in the source XMP causes + /// the corresponding destination XMP property to be deleted. The default is to treat empty + /// values the same as non-empty values. An empty value is any of a simple empty string, an array + /// with no items, or a struct with no fields. Qualifiers are ignored. + ///
+ ///

The detailed behavior is defined by the following pseudo-code: + ///

+ ///
+        /// appendProperties ( sourceXMP, destXMP, doAllProperties,
+        /// replaceOldValues, deleteEmptyValues ):
+        /// for all source schema (top level namespaces):
+        /// for all top level properties in sourceSchema:
+        /// if doAllProperties or prop is external:
+        /// appendSubtree ( sourceNode, destSchema, replaceOldValues, deleteEmptyValues )
+        /// appendSubtree ( sourceNode, destParent, replaceOldValues, deleteEmptyValues ):
+        /// if deleteEmptyValues and source value is empty:
+        /// delete the corresponding child from destParent
+        /// else if sourceNode not in destParent (by name):
+        /// copy sourceNode's subtree to destParent
+        /// else if replaceOld:
+        /// delete subtree from destParent
+        /// copy sourceNode's subtree to destParent
+        /// else:
+        /// // Already exists in dest and not replacing, merge structs and arrays
+        /// if sourceNode and destNode forms differ:
+        /// return, leave the destNode alone
+        /// else if form is a struct:
+        /// for each field in sourceNode:
+        /// AppendSubtree ( sourceNode.field, destNode, replaceOldValues )
+        /// else if form is an alt-text array:
+        /// copy new items by "xml:lang" value into the destination
+        /// else if form is an array:
+        /// copy new items by value into the destination, ignoring order and duplicates
+        /// 
+ ///
+ ///

Note: appendProperties can be expensive if replaceOldValues is not passed and + /// the XMP contains large arrays. The array item checking described above is n-squared. + /// Each source item is checked to see if it already exists in the destination, + /// without regard to order or duplicates. + ///

Simple items are compared by value and "xml:lang" qualifier, other qualifiers are ignored. + /// Structs are recursively compared by field names, without regard to field order. Arrays are + /// compared by recursively comparing all items. + /// + /// The source XMP object. + /// The destination XMP object. + /// Do internal properties in addition to external properties. + /// Replace the values of existing properties. + /// Delete destination values if source property is empty. + /// Forwards the Exceptions from the metadata processing + /// + public static void AppendProperties(XMPMeta source, XMPMeta dest, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) + { + XMPUtilsImpl.AppendProperties(source, dest, doAllProperties, replaceOldValues, deleteEmptyValues); + } + + ///

Convert from string to Boolean. + /// The string representation of the Boolean. + /// + /// The appropriate boolean value for the string. The checked values + /// for true and false are: + ///
    + ///
  • + /// + /// and + /// + ///
  • "t" and "f" + ///
  • "on" and "off" + ///
  • "yes" and "no" + ///
  • "value <> 0" and "value == 0" + ///
+ ///
+ /// If an empty string is passed. + /// + public static bool ConvertToBoolean(string value) + { + if (value == null || value.Length == 0) + { + throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); + } + value = value.ToLower(); + try + { + // First try interpretation as Integer (anything not 0 is true) + return Convert.ToInt32(value) != 0; + } + catch (FormatException) + { + return "true".Equals(value) || "t".Equals(value) || "on".Equals(value) || "yes".Equals(value); + } + } + + /// Convert from boolean to string. + /// a boolean value + /// + /// The XMP string representation of the boolean. The values used are + /// given by the constnts + /// + /// and + /// + /// . + /// + public static string ConvertFromBoolean(bool value) + { + return value ? XMPConstConstants.Truestr : XMPConstConstants.Falsestr; + } + + /// Converts a string value to an int. + /// the string value + /// Returns an int. + /// + /// If the rawValue is null or empty or the + /// conversion fails. + /// + /// + public static int ConvertToInteger(string rawValue) + { + try + { + if (rawValue == null || rawValue.Length == 0) + { + throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); + } + if (rawValue.StartsWith("0x")) + { + return Convert.ToInt32(Runtime.Substring(rawValue, 2), 16); + } + else + { + return Convert.ToInt32(rawValue); + } + } + catch (FormatException) + { + throw new XMPException("Invalid integer string", XMPErrorConstants.Badvalue); + } + } + + /// Convert from int to string. + /// an int value + /// The string representation of the int. + public static string ConvertFromInteger(int value) + { + return value.ToString(); + } + + /// Converts a string value to a long. + /// the string value + /// Returns a long. + /// + /// If the rawValue is null or empty or the + /// conversion fails. + /// + /// + public static long ConvertToLong(string rawValue) + { + try + { + if (rawValue == null || rawValue.Length == 0) + { + throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); + } + if (rawValue.StartsWith("0x")) + { + return Convert.ToInt64(Runtime.Substring(rawValue, 2), 16); + } + else + { + return Convert.ToInt64(rawValue); + } + } + catch (FormatException) + { + throw new XMPException("Invalid long string", XMPErrorConstants.Badvalue); + } + } + + /// Convert from long to string. + /// a long value + /// The string representation of the long. + public static string ConvertFromLong(long value) + { + return value.ToString(); + } + + /// Converts a string value to a double. + /// the string value + /// Returns a double. + /// + /// If the rawValue is null or empty or the + /// conversion fails. + /// + /// + public static double ConvertToDouble(string rawValue) + { + try + { + if (rawValue == null || rawValue.Length == 0) + { + throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); + } + else + { + return double.Parse(rawValue); + } + } + catch (FormatException) + { + throw new XMPException("Invalid double string", XMPErrorConstants.Badvalue); + } + } + + /// Convert from long to string. + /// a long value + /// The string representation of the long. + public static string ConvertFromDouble(double value) + { + return value.ToString(); + } + + /// Converts a string value to an XMPDateTime. + /// the string value + /// Returns an XMPDateTime-object. + /// + /// If the rawValue is null or empty or the + /// conversion fails. + /// + /// + public static XMPDateTime ConvertToDate(string rawValue) + { + if (rawValue == null || rawValue.Length == 0) + { + throw new XMPException("Empty convert-string", XMPErrorConstants.Badvalue); + } + else + { + return ISO8601Converter.Parse(rawValue); + } + } + + /// Convert from XMPDateTime to string. + /// an XMPDateTime + /// The string representation of the long. + public static string ConvertFromDate(XMPDateTime value) + { + return ISO8601Converter.Render(value); + } + + /// Convert from a byte array to a base64 encoded string. + /// the byte array to be converted + /// Returns the base64 string. + public static string EncodeBase64(sbyte[] buffer) + { + return Runtime.GetStringForBytes(Base64.Encode(buffer)); + } + + /// Decode from Base64 encoded string to raw data. + /// a base64 encoded string + /// Returns a byte array containg the decoded string. + /// Thrown if the given string is not property base64 encoded + /// + public static sbyte[] DecodeBase64(string base64String) + { + try + { + return Base64.Decode(Runtime.GetBytesForString(base64String)); + } + catch (Exception e) + { + throw new XMPException("Invalid base64 string", XMPErrorConstants.Badvalue, e); + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/XMPVersionInfo.cs b/Com.Adobe.Xmp/Com/adobe/xmp/XMPVersionInfo.cs index 893526f02..fa32699e5 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/XMPVersionInfo.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/XMPVersionInfo.cs @@ -1,43 +1,43 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -namespace Com.Adobe.Xmp -{ - /// XMP Toolkit Version Information. - /// - /// XMP Toolkit Version Information. - ///

- /// Version information for the XMP toolkit is stored in the jar-library and available through a - /// runtime call, - /// - /// , addition static version numbers are - /// defined in "version.properties". - /// - /// 23.01.2006 - public interface XMPVersionInfo - { - /// Returns the primary release number, the "1" in version "1.2.3". - int GetMajor(); - - /// Returns the secondary release number, the "2" in version "1.2.3". - int GetMinor(); - - /// Returns the tertiary release number, the "3" in version "1.2.3". - int GetMicro(); - - /// Returns a rolling build number, monotonically increasing in a release. - int GetBuild(); - - /// Returns true if this is a debug build. - bool IsDebug(); - - /// Returns a comprehensive version information string. - string GetMessage(); - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +namespace Com.Adobe.Xmp +{ + ///

XMP Toolkit Version Information. + /// + /// XMP Toolkit Version Information. + ///

+ /// Version information for the XMP toolkit is stored in the jar-library and available through a + /// runtime call, + /// + /// , addition static version numbers are + /// defined in "version.properties". + /// + /// 23.01.2006 + public interface XMPVersionInfo + { + /// Returns the primary release number, the "1" in version "1.2.3". + int GetMajor(); + + /// Returns the secondary release number, the "2" in version "1.2.3". + int GetMinor(); + + /// Returns the tertiary release number, the "3" in version "1.2.3". + int GetMicro(); + + /// Returns a rolling build number, monotonically increasing in a release. + int GetBuild(); + + /// Returns true if this is a debug build. + bool IsDebug(); + + /// Returns a comprehensive version information string. + string GetMessage(); + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/Base64.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/Base64.cs index abf2003a8..470763fdb 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/Base64.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/Base64.cs @@ -1,243 +1,243 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2001 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - ///

- /// A utility class to perform base64 encoding and decoding as specified - /// in RFC-1521. - /// - /// - /// A utility class to perform base64 encoding and decoding as specified - /// in RFC-1521. See also RFC 1421. - /// - /// $Revision: 1.4 $ - public class Base64 - { - /// marker for invalid bytes - private const sbyte Invalid = unchecked((sbyte)(-1)); - - /// marker for accepted whitespace bytes - private const sbyte Whitespace = unchecked((sbyte)(-2)); - - /// marker for an equal symbol - private const sbyte Equal = unchecked((sbyte)(-3)); - - private static sbyte[] base64 = new sbyte[] { unchecked((sbyte)(byte)('A')), unchecked((sbyte)(byte)('B')), unchecked((sbyte)(byte)('C')), unchecked((sbyte)(byte)('D')), unchecked((sbyte)(byte)('E')), unchecked((sbyte)(byte)('F')), unchecked( - (sbyte)(byte)('G')), unchecked((sbyte)(byte)('H')), unchecked((sbyte)(byte)('I')), unchecked((sbyte)(byte)('J')), unchecked((sbyte)(byte)('K')), unchecked((sbyte)(byte)('L')), unchecked((sbyte)(byte)('M')), unchecked((sbyte)(byte)('N')), unchecked( - (sbyte)(byte)('O')), unchecked((sbyte)(byte)('P')), unchecked((sbyte)(byte)('Q')), unchecked((sbyte)(byte)('R')), unchecked((sbyte)(byte)('S')), unchecked((sbyte)(byte)('T')), unchecked((sbyte)(byte)('U')), unchecked((sbyte)(byte)('V')), unchecked( - (sbyte)(byte)('W')), unchecked((sbyte)(byte)('X')), unchecked((sbyte)(byte)('Y')), unchecked((sbyte)(byte)('Z')), unchecked((sbyte)(byte)('a')), unchecked((sbyte)(byte)('b')), unchecked((sbyte)(byte)('c')), unchecked((sbyte)(byte)('d')), unchecked( - (sbyte)(byte)('e')), unchecked((sbyte)(byte)('f')), unchecked((sbyte)(byte)('g')), unchecked((sbyte)(byte)('h')), unchecked((sbyte)(byte)('i')), unchecked((sbyte)(byte)('j')), unchecked((sbyte)(byte)('k')), unchecked((sbyte)(byte)('l')), unchecked( - (sbyte)(byte)('m')), unchecked((sbyte)(byte)('n')), unchecked((sbyte)(byte)('o')), unchecked((sbyte)(byte)('p')), unchecked((sbyte)(byte)('q')), unchecked((sbyte)(byte)('r')), unchecked((sbyte)(byte)('s')), unchecked((sbyte)(byte)('t')), unchecked( - (sbyte)(byte)('u')), unchecked((sbyte)(byte)('v')), unchecked((sbyte)(byte)('w')), unchecked((sbyte)(byte)('x')), unchecked((sbyte)(byte)('y')), unchecked((sbyte)(byte)('z')), unchecked((sbyte)(byte)('0')), unchecked((sbyte)(byte)('1')), unchecked( - (sbyte)(byte)('2')), unchecked((sbyte)(byte)('3')), unchecked((sbyte)(byte)('4')), unchecked((sbyte)(byte)('5')), unchecked((sbyte)(byte)('6')), unchecked((sbyte)(byte)('7')), unchecked((sbyte)(byte)('8')), unchecked((sbyte)(byte)('9')), unchecked( - (sbyte)(byte)('+')), unchecked((sbyte)(byte)('/')) }; - - private static sbyte[] ascii = new sbyte[255]; - - static Base64() - { - // 0 to 3 - // 4 to 7 - // 8 to 11 - // 11 to 15 - // 16 to 19 - // 20 to 23 - // 24 to 27 - // 28 to 31 - // 32 to 35 - // 36 to 39 - // 40 to 43 - // 44 to 47 - // 48 to 51 - // 52 to 55 - // 56 to 59 - // 60 to 63 - // not valid bytes - for (int idx = 0; idx < 255; idx++) - { - ascii[idx] = Invalid; - } - // valid bytes - for (int idx_1 = 0; idx_1 < base64.Length; idx_1++) - { - ascii[base64[idx_1]] = unchecked((sbyte)idx_1); - } - // whitespaces - ascii[unchecked((int)(0x09))] = Whitespace; - ascii[unchecked((int)(0x0A))] = Whitespace; - ascii[unchecked((int)(0x0D))] = Whitespace; - ascii[unchecked((int)(0x20))] = Whitespace; - // trailing equals - ascii[unchecked((int)(0x3d))] = Equal; - } - - /// Encode the given byte[]. - /// the source string. - /// the base64-encoded data. - public static sbyte[] Encode(sbyte[] src) - { - return Encode(src, 0); - } - - /// Encode the given byte[]. - /// the source string. - /// - /// a linefeed is added after linefeed characters; - /// must be dividable by four; 0 means no linefeeds - /// - /// the base64-encoded data. - public static sbyte[] Encode(sbyte[] src, int lineFeed) - { - // linefeed must be dividable by 4 - lineFeed = lineFeed / 4 * 4; - if (lineFeed < 0) - { - lineFeed = 0; - } - // determine code length - int codeLength = ((src.Length + 2) / 3) * 4; - if (lineFeed > 0) - { - codeLength += (codeLength - 1) / lineFeed; - } - sbyte[] dst = new sbyte[codeLength]; - int bits24; - int bits6; - // - // Do 3-byte to 4-byte conversion + 0-63 to ascii printable conversion - // - int didx = 0; - int sidx = 0; - int lf = 0; - while (sidx + 3 <= src.Length) - { - bits24 = (src[sidx++] & unchecked((int)(0xFF))) << 16; - bits24 |= (src[sidx++] & unchecked((int)(0xFF))) << 8; - bits24 |= (src[sidx++] & unchecked((int)(0xFF))) << 0; - bits6 = (bits24 & unchecked((int)(0x00FC0000))) >> 18; - dst[didx++] = base64[bits6]; - bits6 = (bits24 & unchecked((int)(0x0003F000))) >> 12; - dst[didx++] = base64[bits6]; - bits6 = (bits24 & unchecked((int)(0x00000FC0))) >> 6; - dst[didx++] = base64[bits6]; - bits6 = (bits24 & unchecked((int)(0x0000003F))); - dst[didx++] = base64[bits6]; - lf += 4; - if (didx < codeLength && lineFeed > 0 && lf % lineFeed == 0) - { - dst[didx++] = unchecked((int)(0x0A)); - } - } - if (src.Length - sidx == 2) - { - bits24 = (src[sidx] & unchecked((int)(0xFF))) << 16; - bits24 |= (src[sidx + 1] & unchecked((int)(0xFF))) << 8; - bits6 = (bits24 & unchecked((int)(0x00FC0000))) >> 18; - dst[didx++] = base64[bits6]; - bits6 = (bits24 & unchecked((int)(0x0003F000))) >> 12; - dst[didx++] = base64[bits6]; - bits6 = (bits24 & unchecked((int)(0x00000FC0))) >> 6; - dst[didx++] = base64[bits6]; - dst[didx++] = unchecked((sbyte)(byte)('=')); - } - else - { - if (src.Length - sidx == 1) - { - bits24 = (src[sidx] & unchecked((int)(0xFF))) << 16; - bits6 = (bits24 & unchecked((int)(0x00FC0000))) >> 18; - dst[didx++] = base64[bits6]; - bits6 = (bits24 & unchecked((int)(0x0003F000))) >> 12; - dst[didx++] = base64[bits6]; - dst[didx++] = unchecked((sbyte)(byte)('=')); - dst[didx++] = unchecked((sbyte)(byte)('=')); - } - } - return dst; - } - - /// Encode the given string. - /// the source string. - /// the base64-encoded string. - public static string Encode(string src) - { - return Runtime.GetStringForBytes(Encode(Runtime.GetBytesForString(src))); - } - - /// Decode the given byte[]. - /// the base64-encoded data. - /// the decoded data. - /// - /// Thrown if the base 64 strings contains non-valid characters, - /// beside the bas64 chars, LF, CR, tab and space are accepted. - /// - public static sbyte[] Decode(sbyte[] src) - { - // - // Do ascii printable to 0-63 conversion. - // - int sidx; - int srcLen = 0; - for (sidx = 0; sidx < src.Length; sidx++) - { - sbyte val = ascii[src[sidx]]; - if (val >= 0) - { - src[srcLen++] = val; - } - else - { - if (val == Invalid) - { - throw new ArgumentException("Invalid base 64 string"); - } - } - } - // - // Trim any padding. - // - while (srcLen > 0 && src[srcLen - 1] == Equal) - { - srcLen--; - } - sbyte[] dst = new sbyte[srcLen * 3 / 4]; - // - // Do 4-byte to 3-byte conversion. - // - int didx; - for (sidx = 0, didx = 0; didx < dst.Length - 2; sidx += 4, didx += 3) - { - dst[didx] = unchecked((sbyte)(((src[sidx] << 2) & unchecked((int)(0xFF))) | ((src[sidx + 1] >> 4) & unchecked((int)(0x03))))); - dst[didx + 1] = unchecked((sbyte)(((src[sidx + 1] << 4) & unchecked((int)(0xFF))) | ((src[sidx + 2] >> 2) & unchecked((int)(0x0F))))); - dst[didx + 2] = unchecked((sbyte)(((src[sidx + 2] << 6) & unchecked((int)(0xFF))) | ((src[sidx + 3]) & unchecked((int)(0x3F))))); - } - if (didx < dst.Length) - { - dst[didx] = unchecked((sbyte)(((src[sidx] << 2) & unchecked((int)(0xFF))) | ((src[sidx + 1] >> 4) & unchecked((int)(0x03))))); - } - if (++didx < dst.Length) - { - dst[didx] = unchecked((sbyte)(((src[sidx + 1] << 4) & unchecked((int)(0xFF))) | ((src[sidx + 2] >> 2) & unchecked((int)(0x0F))))); - } - return dst; - } - - /// Decode the given string. - /// the base64-encoded string. - /// the decoded string. - public static string Decode(string src) - { - return Runtime.GetStringForBytes(Decode(Runtime.GetBytesForString(src))); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2001 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// + /// A utility class to perform base64 encoding and decoding as specified + /// in RFC-1521. + /// + /// + /// A utility class to perform base64 encoding and decoding as specified + /// in RFC-1521. See also RFC 1421. + /// + /// $Revision: 1.4 $ + public class Base64 + { + /// marker for invalid bytes + private const sbyte Invalid = unchecked((sbyte)(-1)); + + /// marker for accepted whitespace bytes + private const sbyte Whitespace = unchecked((sbyte)(-2)); + + /// marker for an equal symbol + private const sbyte Equal = unchecked((sbyte)(-3)); + + private static sbyte[] base64 = new sbyte[] { unchecked((sbyte)(byte)('A')), unchecked((sbyte)(byte)('B')), unchecked((sbyte)(byte)('C')), unchecked((sbyte)(byte)('D')), unchecked((sbyte)(byte)('E')), unchecked((sbyte)(byte)('F')), unchecked( + (sbyte)(byte)('G')), unchecked((sbyte)(byte)('H')), unchecked((sbyte)(byte)('I')), unchecked((sbyte)(byte)('J')), unchecked((sbyte)(byte)('K')), unchecked((sbyte)(byte)('L')), unchecked((sbyte)(byte)('M')), unchecked((sbyte)(byte)('N')), unchecked( + (sbyte)(byte)('O')), unchecked((sbyte)(byte)('P')), unchecked((sbyte)(byte)('Q')), unchecked((sbyte)(byte)('R')), unchecked((sbyte)(byte)('S')), unchecked((sbyte)(byte)('T')), unchecked((sbyte)(byte)('U')), unchecked((sbyte)(byte)('V')), unchecked( + (sbyte)(byte)('W')), unchecked((sbyte)(byte)('X')), unchecked((sbyte)(byte)('Y')), unchecked((sbyte)(byte)('Z')), unchecked((sbyte)(byte)('a')), unchecked((sbyte)(byte)('b')), unchecked((sbyte)(byte)('c')), unchecked((sbyte)(byte)('d')), unchecked( + (sbyte)(byte)('e')), unchecked((sbyte)(byte)('f')), unchecked((sbyte)(byte)('g')), unchecked((sbyte)(byte)('h')), unchecked((sbyte)(byte)('i')), unchecked((sbyte)(byte)('j')), unchecked((sbyte)(byte)('k')), unchecked((sbyte)(byte)('l')), unchecked( + (sbyte)(byte)('m')), unchecked((sbyte)(byte)('n')), unchecked((sbyte)(byte)('o')), unchecked((sbyte)(byte)('p')), unchecked((sbyte)(byte)('q')), unchecked((sbyte)(byte)('r')), unchecked((sbyte)(byte)('s')), unchecked((sbyte)(byte)('t')), unchecked( + (sbyte)(byte)('u')), unchecked((sbyte)(byte)('v')), unchecked((sbyte)(byte)('w')), unchecked((sbyte)(byte)('x')), unchecked((sbyte)(byte)('y')), unchecked((sbyte)(byte)('z')), unchecked((sbyte)(byte)('0')), unchecked((sbyte)(byte)('1')), unchecked( + (sbyte)(byte)('2')), unchecked((sbyte)(byte)('3')), unchecked((sbyte)(byte)('4')), unchecked((sbyte)(byte)('5')), unchecked((sbyte)(byte)('6')), unchecked((sbyte)(byte)('7')), unchecked((sbyte)(byte)('8')), unchecked((sbyte)(byte)('9')), unchecked( + (sbyte)(byte)('+')), unchecked((sbyte)(byte)('/')) }; + + private static sbyte[] ascii = new sbyte[255]; + + static Base64() + { + // 0 to 3 + // 4 to 7 + // 8 to 11 + // 11 to 15 + // 16 to 19 + // 20 to 23 + // 24 to 27 + // 28 to 31 + // 32 to 35 + // 36 to 39 + // 40 to 43 + // 44 to 47 + // 48 to 51 + // 52 to 55 + // 56 to 59 + // 60 to 63 + // not valid bytes + for (int idx = 0; idx < 255; idx++) + { + ascii[idx] = Invalid; + } + // valid bytes + for (int idx_1 = 0; idx_1 < base64.Length; idx_1++) + { + ascii[base64[idx_1]] = unchecked((sbyte)idx_1); + } + // whitespaces + ascii[unchecked((int)(0x09))] = Whitespace; + ascii[unchecked((int)(0x0A))] = Whitespace; + ascii[unchecked((int)(0x0D))] = Whitespace; + ascii[unchecked((int)(0x20))] = Whitespace; + // trailing equals + ascii[unchecked((int)(0x3d))] = Equal; + } + + /// Encode the given byte[]. + /// the source string. + /// the base64-encoded data. + public static sbyte[] Encode(sbyte[] src) + { + return Encode(src, 0); + } + + /// Encode the given byte[]. + /// the source string. + /// + /// a linefeed is added after linefeed characters; + /// must be dividable by four; 0 means no linefeeds + /// + /// the base64-encoded data. + public static sbyte[] Encode(sbyte[] src, int lineFeed) + { + // linefeed must be dividable by 4 + lineFeed = lineFeed / 4 * 4; + if (lineFeed < 0) + { + lineFeed = 0; + } + // determine code length + int codeLength = ((src.Length + 2) / 3) * 4; + if (lineFeed > 0) + { + codeLength += (codeLength - 1) / lineFeed; + } + sbyte[] dst = new sbyte[codeLength]; + int bits24; + int bits6; + // + // Do 3-byte to 4-byte conversion + 0-63 to ascii printable conversion + // + int didx = 0; + int sidx = 0; + int lf = 0; + while (sidx + 3 <= src.Length) + { + bits24 = (src[sidx++] & unchecked((int)(0xFF))) << 16; + bits24 |= (src[sidx++] & unchecked((int)(0xFF))) << 8; + bits24 |= (src[sidx++] & unchecked((int)(0xFF))) << 0; + bits6 = (bits24 & unchecked((int)(0x00FC0000))) >> 18; + dst[didx++] = base64[bits6]; + bits6 = (bits24 & unchecked((int)(0x0003F000))) >> 12; + dst[didx++] = base64[bits6]; + bits6 = (bits24 & unchecked((int)(0x00000FC0))) >> 6; + dst[didx++] = base64[bits6]; + bits6 = (bits24 & unchecked((int)(0x0000003F))); + dst[didx++] = base64[bits6]; + lf += 4; + if (didx < codeLength && lineFeed > 0 && lf % lineFeed == 0) + { + dst[didx++] = unchecked((int)(0x0A)); + } + } + if (src.Length - sidx == 2) + { + bits24 = (src[sidx] & unchecked((int)(0xFF))) << 16; + bits24 |= (src[sidx + 1] & unchecked((int)(0xFF))) << 8; + bits6 = (bits24 & unchecked((int)(0x00FC0000))) >> 18; + dst[didx++] = base64[bits6]; + bits6 = (bits24 & unchecked((int)(0x0003F000))) >> 12; + dst[didx++] = base64[bits6]; + bits6 = (bits24 & unchecked((int)(0x00000FC0))) >> 6; + dst[didx++] = base64[bits6]; + dst[didx++] = unchecked((sbyte)(byte)('=')); + } + else + { + if (src.Length - sidx == 1) + { + bits24 = (src[sidx] & unchecked((int)(0xFF))) << 16; + bits6 = (bits24 & unchecked((int)(0x00FC0000))) >> 18; + dst[didx++] = base64[bits6]; + bits6 = (bits24 & unchecked((int)(0x0003F000))) >> 12; + dst[didx++] = base64[bits6]; + dst[didx++] = unchecked((sbyte)(byte)('=')); + dst[didx++] = unchecked((sbyte)(byte)('=')); + } + } + return dst; + } + + /// Encode the given string. + /// the source string. + /// the base64-encoded string. + public static string Encode(string src) + { + return Runtime.GetStringForBytes(Encode(Runtime.GetBytesForString(src))); + } + + /// Decode the given byte[]. + /// the base64-encoded data. + /// the decoded data. + /// + /// Thrown if the base 64 strings contains non-valid characters, + /// beside the bas64 chars, LF, CR, tab and space are accepted. + /// + public static sbyte[] Decode(sbyte[] src) + { + // + // Do ascii printable to 0-63 conversion. + // + int sidx; + int srcLen = 0; + for (sidx = 0; sidx < src.Length; sidx++) + { + sbyte val = ascii[src[sidx]]; + if (val >= 0) + { + src[srcLen++] = val; + } + else + { + if (val == Invalid) + { + throw new ArgumentException("Invalid base 64 string"); + } + } + } + // + // Trim any padding. + // + while (srcLen > 0 && src[srcLen - 1] == Equal) + { + srcLen--; + } + sbyte[] dst = new sbyte[srcLen * 3 / 4]; + // + // Do 4-byte to 3-byte conversion. + // + int didx; + for (sidx = 0, didx = 0; didx < dst.Length - 2; sidx += 4, didx += 3) + { + dst[didx] = unchecked((sbyte)(((src[sidx] << 2) & unchecked((int)(0xFF))) | ((src[sidx + 1] >> 4) & unchecked((int)(0x03))))); + dst[didx + 1] = unchecked((sbyte)(((src[sidx + 1] << 4) & unchecked((int)(0xFF))) | ((src[sidx + 2] >> 2) & unchecked((int)(0x0F))))); + dst[didx + 2] = unchecked((sbyte)(((src[sidx + 2] << 6) & unchecked((int)(0xFF))) | ((src[sidx + 3]) & unchecked((int)(0x3F))))); + } + if (didx < dst.Length) + { + dst[didx] = unchecked((sbyte)(((src[sidx] << 2) & unchecked((int)(0xFF))) | ((src[sidx + 1] >> 4) & unchecked((int)(0x03))))); + } + if (++didx < dst.Length) + { + dst[didx] = unchecked((sbyte)(((src[sidx + 1] << 4) & unchecked((int)(0xFF))) | ((src[sidx + 2] >> 2) & unchecked((int)(0x0F))))); + } + return dst; + } + + /// Decode the given string. + /// the base64-encoded string. + /// the decoded string. + public static string Decode(string src) + { + return Runtime.GetStringForBytes(Decode(Runtime.GetBytesForString(src))); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ByteBuffer.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ByteBuffer.cs index 992f8192b..5a36d3fe8 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ByteBuffer.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ByteBuffer.cs @@ -1,291 +1,291 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// Byte buffer container including length of valid data. - /// 11.10.2006 - public class ByteBuffer - { - private sbyte[] buffer; - - private int length; - - private string encoding = null; - - /// the initial capacity for this buffer - public ByteBuffer(int initialCapacity) - { - this.buffer = new sbyte[initialCapacity]; - this.length = 0; - } - - /// a byte array that will be wrapped with ByteBuffer. - public ByteBuffer(sbyte[] buffer) - { - this.buffer = buffer; - this.length = buffer.Length; - } - - /// a byte array that will be wrapped with ByteBuffer. - /// the length of valid bytes in the array - public ByteBuffer(sbyte[] buffer, int length) - { - if (length > buffer.Length) - { - throw new IndexOutOfRangeException("Valid length exceeds the buffer length."); - } - this.buffer = buffer; - this.length = length; - } - - /// Loads the stream into a buffer. - /// an InputStream - /// If the stream cannot be read. - public ByteBuffer(InputStream @in) - { - // load stream into buffer - int chunk = 16384; - this.length = 0; - this.buffer = new sbyte[chunk]; - int read; - while ((read = @in.Read(this.buffer, this.length, chunk)) > 0) - { - this.length += read; - if (read == chunk) - { - EnsureCapacity(length + chunk); - } - else - { - break; - } - } - } - - /// a byte array that will be wrapped with ByteBuffer. - /// the offset of the provided buffer. - /// the length of valid bytes in the array - public ByteBuffer(sbyte[] buffer, int offset, int length) - { - if (length > buffer.Length - offset) - { - throw new IndexOutOfRangeException("Valid length exceeds the buffer length."); - } - this.buffer = new sbyte[length]; - Array.Copy(buffer, offset, this.buffer, 0, length); - this.length = length; - } - - /// Returns a byte stream that is limited to the valid amount of bytes. - public virtual InputStream GetByteStream() - { - return new ByteArrayInputStream(buffer, 0, length); - } - - /// - /// Returns the length, that means the number of valid bytes, of the buffer; - /// the inner byte array might be bigger than that. - /// - public virtual int Length() - { - return length; - } - - // /** - // * Note: Only the byte up to length are valid! - // * @return Returns the inner byte buffer. - // */ - // public byte[] getBuffer() - // { - // return buffer; - // } - /// the index to retrieve the byte from - /// Returns a byte from the buffer - public virtual sbyte ByteAt(int index) - { - if (index < length) - { - return buffer[index]; - } - else - { - throw new IndexOutOfRangeException("The index exceeds the valid buffer area"); - } - } - - /// the index to retrieve a byte as int or char. - /// Returns a byte from the buffer - public virtual int CharAt(int index) - { - if (index < length) - { - return buffer[index] & unchecked((int)(0xFF)); - } - else - { - throw new IndexOutOfRangeException("The index exceeds the valid buffer area"); - } - } - - /// Appends a byte to the buffer. - /// a byte - public virtual void Append(sbyte b) - { - EnsureCapacity(length + 1); - buffer[length++] = b; - } - - /// Appends a byte array or part of to the buffer. - /// a byte array - /// an offset with - /// - public virtual void Append(sbyte[] bytes, int offset, int len) - { - EnsureCapacity(length + len); - Array.Copy(bytes, offset, buffer, length, len); - length += len; - } - - /// Append a byte array to the buffer - /// a byte array - public virtual void Append(sbyte[] bytes) - { - Append(bytes, 0, bytes.Length); - } - - /// Append another buffer to this buffer. - /// another ByteBuffer - public virtual void Append(ByteBuffer anotherBuffer) - { - Append(anotherBuffer.buffer, 0, anotherBuffer.length); - } - - /// Detects the encoding of the byte buffer, stores and returns it. - /// - /// Detects the encoding of the byte buffer, stores and returns it. - /// Only UTF-8, UTF-16LE/BE and UTF-32LE/BE are recognized. - /// Note: UTF-32 flavors are not supported by Java, the XML-parser will complain. - /// - /// Returns the encoding string. - public virtual string GetEncoding() - { - if (encoding == null) - { - // needs four byte at maximum to determine encoding - if (length < 2) - { - // only one byte length must be UTF-8 - encoding = "UTF-8"; - } - else - { - if (buffer[0] == 0) - { - // These cases are: - // 00 nn -- -- - Big endian UTF-16 - // 00 00 00 nn - Big endian UTF-32 - // 00 00 FE FF - Big endian UTF 32 - if (length < 4 || buffer[1] != 0) - { - encoding = "UTF-16BE"; - } - else - { - if ((buffer[2] & unchecked((int)(0xFF))) == unchecked((int)(0xFE)) && (buffer[3] & unchecked((int)(0xFF))) == unchecked((int)(0xFF))) - { - encoding = "UTF-32BE"; - } - else - { - encoding = "UTF-32"; - } - } - } - else - { - if ((buffer[0] & unchecked((int)(0xFF))) < unchecked((int)(0x80))) - { - // These cases are: - // nn mm -- -- - UTF-8, includes EF BB BF case - // nn 00 -- -- - Little endian UTF-16 - if (buffer[1] != 0) - { - encoding = "UTF-8"; - } - else - { - if (length < 4 || buffer[2] != 0) - { - encoding = "UTF-16LE"; - } - else - { - encoding = "UTF-32LE"; - } - } - } - else - { - // These cases are: - // EF BB BF -- - UTF-8 - // FE FF -- -- - Big endian UTF-16 - // FF FE 00 00 - Little endian UTF-32 - // FF FE -- -- - Little endian UTF-16 - if ((buffer[0] & unchecked((int)(0xFF))) == unchecked((int)(0xEF))) - { - encoding = "UTF-8"; - } - else - { - if ((buffer[0] & unchecked((int)(0xFF))) == unchecked((int)(0xFE))) - { - encoding = "UTF-16"; - } - else - { - // in fact BE - if (length < 4 || buffer[2] != 0) - { - encoding = "UTF-16"; - } - else - { - // in fact LE - encoding = "UTF-32"; - } - } - } - } - } - } - } - // in fact LE - return encoding; - } - - /// - /// Ensures the requested capacity by increasing the buffer size when the - /// current length is exceeded. - /// - /// requested new buffer length - private void EnsureCapacity(int requestedLength) - { - if (requestedLength > buffer.Length) - { - sbyte[] oldBuf = buffer; - buffer = new sbyte[oldBuf.Length * 2]; - Array.Copy(oldBuf, 0, buffer, 0, oldBuf.Length); - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// Byte buffer container including length of valid data. + /// 11.10.2006 + public class ByteBuffer + { + private sbyte[] buffer; + + private int length; + + private string encoding = null; + + /// the initial capacity for this buffer + public ByteBuffer(int initialCapacity) + { + this.buffer = new sbyte[initialCapacity]; + this.length = 0; + } + + /// a byte array that will be wrapped with ByteBuffer. + public ByteBuffer(sbyte[] buffer) + { + this.buffer = buffer; + this.length = buffer.Length; + } + + /// a byte array that will be wrapped with ByteBuffer. + /// the length of valid bytes in the array + public ByteBuffer(sbyte[] buffer, int length) + { + if (length > buffer.Length) + { + throw new IndexOutOfRangeException("Valid length exceeds the buffer length."); + } + this.buffer = buffer; + this.length = length; + } + + /// Loads the stream into a buffer. + /// an InputStream + /// If the stream cannot be read. + public ByteBuffer(InputStream @in) + { + // load stream into buffer + int chunk = 16384; + this.length = 0; + this.buffer = new sbyte[chunk]; + int read; + while ((read = @in.Read(this.buffer, this.length, chunk)) > 0) + { + this.length += read; + if (read == chunk) + { + EnsureCapacity(length + chunk); + } + else + { + break; + } + } + } + + /// a byte array that will be wrapped with ByteBuffer. + /// the offset of the provided buffer. + /// the length of valid bytes in the array + public ByteBuffer(sbyte[] buffer, int offset, int length) + { + if (length > buffer.Length - offset) + { + throw new IndexOutOfRangeException("Valid length exceeds the buffer length."); + } + this.buffer = new sbyte[length]; + Array.Copy(buffer, offset, this.buffer, 0, length); + this.length = length; + } + + /// Returns a byte stream that is limited to the valid amount of bytes. + public virtual InputStream GetByteStream() + { + return new ByteArrayInputStream(buffer, 0, length); + } + + /// + /// Returns the length, that means the number of valid bytes, of the buffer; + /// the inner byte array might be bigger than that. + /// + public virtual int Length() + { + return length; + } + + // /** + // * Note: Only the byte up to length are valid! + // * @return Returns the inner byte buffer. + // */ + // public byte[] getBuffer() + // { + // return buffer; + // } + /// the index to retrieve the byte from + /// Returns a byte from the buffer + public virtual sbyte ByteAt(int index) + { + if (index < length) + { + return buffer[index]; + } + else + { + throw new IndexOutOfRangeException("The index exceeds the valid buffer area"); + } + } + + /// the index to retrieve a byte as int or char. + /// Returns a byte from the buffer + public virtual int CharAt(int index) + { + if (index < length) + { + return buffer[index] & unchecked((int)(0xFF)); + } + else + { + throw new IndexOutOfRangeException("The index exceeds the valid buffer area"); + } + } + + /// Appends a byte to the buffer. + /// a byte + public virtual void Append(sbyte b) + { + EnsureCapacity(length + 1); + buffer[length++] = b; + } + + /// Appends a byte array or part of to the buffer. + /// a byte array + /// an offset with + /// + public virtual void Append(sbyte[] bytes, int offset, int len) + { + EnsureCapacity(length + len); + Array.Copy(bytes, offset, buffer, length, len); + length += len; + } + + /// Append a byte array to the buffer + /// a byte array + public virtual void Append(sbyte[] bytes) + { + Append(bytes, 0, bytes.Length); + } + + /// Append another buffer to this buffer. + /// another ByteBuffer + public virtual void Append(ByteBuffer anotherBuffer) + { + Append(anotherBuffer.buffer, 0, anotherBuffer.length); + } + + /// Detects the encoding of the byte buffer, stores and returns it. + /// + /// Detects the encoding of the byte buffer, stores and returns it. + /// Only UTF-8, UTF-16LE/BE and UTF-32LE/BE are recognized. + /// Note: UTF-32 flavors are not supported by Java, the XML-parser will complain. + /// + /// Returns the encoding string. + public virtual string GetEncoding() + { + if (encoding == null) + { + // needs four byte at maximum to determine encoding + if (length < 2) + { + // only one byte length must be UTF-8 + encoding = "UTF-8"; + } + else + { + if (buffer[0] == 0) + { + // These cases are: + // 00 nn -- -- - Big endian UTF-16 + // 00 00 00 nn - Big endian UTF-32 + // 00 00 FE FF - Big endian UTF 32 + if (length < 4 || buffer[1] != 0) + { + encoding = "UTF-16BE"; + } + else + { + if ((buffer[2] & unchecked((int)(0xFF))) == unchecked((int)(0xFE)) && (buffer[3] & unchecked((int)(0xFF))) == unchecked((int)(0xFF))) + { + encoding = "UTF-32BE"; + } + else + { + encoding = "UTF-32"; + } + } + } + else + { + if ((buffer[0] & unchecked((int)(0xFF))) < unchecked((int)(0x80))) + { + // These cases are: + // nn mm -- -- - UTF-8, includes EF BB BF case + // nn 00 -- -- - Little endian UTF-16 + if (buffer[1] != 0) + { + encoding = "UTF-8"; + } + else + { + if (length < 4 || buffer[2] != 0) + { + encoding = "UTF-16LE"; + } + else + { + encoding = "UTF-32LE"; + } + } + } + else + { + // These cases are: + // EF BB BF -- - UTF-8 + // FE FF -- -- - Big endian UTF-16 + // FF FE 00 00 - Little endian UTF-32 + // FF FE -- -- - Little endian UTF-16 + if ((buffer[0] & unchecked((int)(0xFF))) == unchecked((int)(0xEF))) + { + encoding = "UTF-8"; + } + else + { + if ((buffer[0] & unchecked((int)(0xFF))) == unchecked((int)(0xFE))) + { + encoding = "UTF-16"; + } + else + { + // in fact BE + if (length < 4 || buffer[2] != 0) + { + encoding = "UTF-16"; + } + else + { + // in fact LE + encoding = "UTF-32"; + } + } + } + } + } + } + } + // in fact LE + return encoding; + } + + /// + /// Ensures the requested capacity by increasing the buffer size when the + /// current length is exceeded. + /// + /// requested new buffer length + private void EnsureCapacity(int requestedLength) + { + if (requestedLength > buffer.Length) + { + sbyte[] oldBuf = buffer; + buffer = new sbyte[oldBuf.Length * 2]; + Array.Copy(oldBuf, 0, buffer, 0, oldBuf.Length); + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/CountOutputStream.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/CountOutputStream.cs index 198f512a7..71b7e8104 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/CountOutputStream.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/CountOutputStream.cs @@ -1,64 +1,64 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// An OutputStream that counts the written bytes. - /// 08.11.2006 - public sealed class CountOutputStream : OutputStream - { - /// the decorated output stream - private readonly OutputStream @out; - - /// the byte counter - private int bytesWritten = 0; - - /// Constructor with providing the output stream to decorate. - /// an OutputStream - internal CountOutputStream(OutputStream @out) - { - this.@out = @out; - } - - /// Counts the written bytes. - /// - /// - public override void Write(sbyte[] buf, int off, int len) - { - @out.Write(buf, off, len); - bytesWritten += len; - } - - /// Counts the written bytes. - /// - /// - public override void Write(sbyte[] buf) - { - @out.Write(buf); - bytesWritten += buf.Length; - } - - /// Counts the written bytes. - /// - /// - public override void Write(int b) - { - @out.Write(b); - bytesWritten++; - } - - /// the bytesWritten - public int GetBytesWritten() - { - return bytesWritten; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// An OutputStream that counts the written bytes. + /// 08.11.2006 + public sealed class CountOutputStream : OutputStream + { + /// the decorated output stream + private readonly OutputStream @out; + + /// the byte counter + private int bytesWritten = 0; + + /// Constructor with providing the output stream to decorate. + /// an OutputStream + internal CountOutputStream(OutputStream @out) + { + this.@out = @out; + } + + /// Counts the written bytes. + /// + /// + public override void Write(sbyte[] buf, int off, int len) + { + @out.Write(buf, off, len); + bytesWritten += len; + } + + /// Counts the written bytes. + /// + /// + public override void Write(sbyte[] buf) + { + @out.Write(buf); + bytesWritten += buf.Length; + } + + /// Counts the written bytes. + /// + /// + public override void Write(int b) + { + @out.Write(b); + bytesWritten++; + } + + /// the bytesWritten + public int GetBytesWritten() + { + return bytesWritten; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/FixASCIIControlsReader.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/FixASCIIControlsReader.cs index 648c4bd61..e55c44e01 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/FixASCIIControlsReader.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/FixASCIIControlsReader.cs @@ -1,232 +1,232 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.IO; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// 22.08.2006 - public class FixASCIIControlsReader : PushbackReader - { - private const int StateStart = 0; - - private const int StateAmp = 1; - - private const int StateHash = 2; - - private const int StateHex = 3; - - private const int StateDig1 = 4; - - private const int StateError = 5; - - private const int BufferSize = 8; - - /// the state of the automaton - private int state = StateStart; - - /// the result of the escaping sequence - private int control = 0; - - /// count the digits of the sequence - private int digits = 0; - - /// The look-ahead size is 6 at maximum (&#xAB;) - /// - /// a Reader - public FixASCIIControlsReader(StreamReader @in) - : base(@in, BufferSize) - { - } - - /// - /// - public override int Read(char[] cbuf, int off, int len) - { - int readAhead = 0; - int read = 0; - int pos = off; - char[] readAheadBuffer = new char[BufferSize]; - bool available = true; - while (available && read < len) - { - available = base.Read(readAheadBuffer, readAhead, 1) == 1; - if (available) - { - char c = ProcessChar(readAheadBuffer[readAhead]); - if (state == StateStart) - { - // replace control chars with space - if (Utils.IsControlChar(c)) - { - c = ' '; - } - cbuf[pos++] = c; - readAhead = 0; - read++; - } - else - { - if (state == StateError) - { - Unread(readAheadBuffer, 0, readAhead + 1); - readAhead = 0; - } - else - { - readAhead++; - } - } - } - else - { - if (readAhead > 0) - { - // handles case when file ends within excaped sequence - Unread(readAheadBuffer, 0, readAhead); - state = StateError; - readAhead = 0; - available = true; - } - } - } - return read > 0 || available ? read : -1; - } - - /// Processes numeric escaped chars to find out if they are a control character. - /// a char - /// Returns the char directly or as replacement for the escaped sequence. - private char ProcessChar(char ch) - { - switch (state) - { - case StateStart: - { - if (ch == '&') - { - state = StateAmp; - } - return ch; - } - - case StateAmp: - { - if (ch == '#') - { - state = StateHash; - } - else - { - state = StateError; - } - return ch; - } - - case StateHash: - { - if (ch == 'x') - { - control = 0; - digits = 0; - state = StateHex; - } - else - { - if ('0' <= ch && ch <= '9') - { - control = Extensions.Digit(ch, 10); - digits = 1; - state = StateDig1; - } - else - { - state = StateError; - } - } - return ch; - } - - case StateDig1: - { - if ('0' <= ch && ch <= '9') - { - control = control * 10 + Extensions.Digit(ch, 10); - digits++; - if (digits <= 5) - { - state = StateDig1; - } - else - { - state = StateError; - } - } - else - { - // sequence too long - if (ch == ';' && Utils.IsControlChar((char)control)) - { - state = StateStart; - return (char)control; - } - else - { - state = StateError; - } - } - return ch; - } - - case StateHex: - { - if (('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')) - { - control = control * 16 + Extensions.Digit(ch, 16); - digits++; - if (digits <= 4) - { - state = StateHex; - } - else - { - state = StateError; - } - } - else - { - // sequence too long - if (ch == ';' && Utils.IsControlChar((char)control)) - { - state = StateStart; - return (char)control; - } - else - { - state = StateError; - } - } - return ch; - } - - case StateError: - { - state = StateStart; - return ch; - } - - default: - { - // not reachable - return ch; - } - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.IO; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// 22.08.2006 + public class FixASCIIControlsReader : PushbackReader + { + private const int StateStart = 0; + + private const int StateAmp = 1; + + private const int StateHash = 2; + + private const int StateHex = 3; + + private const int StateDig1 = 4; + + private const int StateError = 5; + + private const int BufferSize = 8; + + /// the state of the automaton + private int state = StateStart; + + /// the result of the escaping sequence + private int control = 0; + + /// count the digits of the sequence + private int digits = 0; + + /// The look-ahead size is 6 at maximum (&#xAB;) + /// + /// a Reader + public FixASCIIControlsReader(StreamReader @in) + : base(@in, BufferSize) + { + } + + /// + /// + public override int Read(char[] cbuf, int off, int len) + { + int readAhead = 0; + int read = 0; + int pos = off; + char[] readAheadBuffer = new char[BufferSize]; + bool available = true; + while (available && read < len) + { + available = base.Read(readAheadBuffer, readAhead, 1) == 1; + if (available) + { + char c = ProcessChar(readAheadBuffer[readAhead]); + if (state == StateStart) + { + // replace control chars with space + if (Utils.IsControlChar(c)) + { + c = ' '; + } + cbuf[pos++] = c; + readAhead = 0; + read++; + } + else + { + if (state == StateError) + { + Unread(readAheadBuffer, 0, readAhead + 1); + readAhead = 0; + } + else + { + readAhead++; + } + } + } + else + { + if (readAhead > 0) + { + // handles case when file ends within excaped sequence + Unread(readAheadBuffer, 0, readAhead); + state = StateError; + readAhead = 0; + available = true; + } + } + } + return read > 0 || available ? read : -1; + } + + /// Processes numeric escaped chars to find out if they are a control character. + /// a char + /// Returns the char directly or as replacement for the escaped sequence. + private char ProcessChar(char ch) + { + switch (state) + { + case StateStart: + { + if (ch == '&') + { + state = StateAmp; + } + return ch; + } + + case StateAmp: + { + if (ch == '#') + { + state = StateHash; + } + else + { + state = StateError; + } + return ch; + } + + case StateHash: + { + if (ch == 'x') + { + control = 0; + digits = 0; + state = StateHex; + } + else + { + if ('0' <= ch && ch <= '9') + { + control = Extensions.Digit(ch, 10); + digits = 1; + state = StateDig1; + } + else + { + state = StateError; + } + } + return ch; + } + + case StateDig1: + { + if ('0' <= ch && ch <= '9') + { + control = control * 10 + Extensions.Digit(ch, 10); + digits++; + if (digits <= 5) + { + state = StateDig1; + } + else + { + state = StateError; + } + } + else + { + // sequence too long + if (ch == ';' && Utils.IsControlChar((char)control)) + { + state = StateStart; + return (char)control; + } + else + { + state = StateError; + } + } + return ch; + } + + case StateHex: + { + if (('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')) + { + control = control * 16 + Extensions.Digit(ch, 16); + digits++; + if (digits <= 4) + { + state = StateHex; + } + else + { + state = StateError; + } + } + else + { + // sequence too long + if (ch == ';' && Utils.IsControlChar((char)control)) + { + state = StateStart; + return (char)control; + } + else + { + state = StateError; + } + } + return ch; + } + + case StateError: + { + state = StateStart; + return ch; + } + + default: + { + // not reachable + return ch; + } + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ISO8601Converter.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ISO8601Converter.cs index 2b366e302..7ca451156 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ISO8601Converter.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ISO8601Converter.cs @@ -1,441 +1,441 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using System.Text; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// Converts between ISO 8601 Strings and Calendar with millisecond resolution. - /// 16.02.2006 - public sealed class ISO8601Converter - { - /// Hides public constructor - private ISO8601Converter() - { - } - - // EMPTY - /// Converts an ISO 8601 string to an XMPDateTime. - /// - /// Converts an ISO 8601 string to an XMPDateTime. - /// Parse a date according to ISO 8601 and - /// http://www.w3.org/TR/NOTE-datetime: - ///
    - ///
  • YYYY - ///
  • YYYY-MM - ///
  • YYYY-MM-DD - ///
  • YYYY-MM-DDThh:mmTZD - ///
  • YYYY-MM-DDThh:mm:ssTZD - ///
  • YYYY-MM-DDThh:mm:ss.sTZD - ///
- /// Data fields: - ///
    - ///
  • YYYY = four-digit year - ///
  • MM = two-digit month (01=January, etc.) - ///
  • DD = two-digit day of month (01 through 31) - ///
  • hh = two digits of hour (00 through 23) - ///
  • mm = two digits of minute (00 through 59) - ///
  • ss = two digits of second (00 through 59) - ///
  • s = one or more digits representing a decimal fraction of a second - ///
  • TZD = time zone designator (Z or +hh:mm or -hh:mm) - ///
- /// Note that ISO 8601 does not seem to allow years less than 1000 or greater - /// than 9999. We allow any year, even negative ones. The year is formatted - /// as "%.4d". - ///

- /// Note: Tolerate missing TZD, assume is UTC. Photoshop 8 writes - /// dates like this for exif:GPSTimeStamp.
- /// Note: DOES NOT APPLY ANYMORE. - /// Tolerate missing date portion, in case someone foolishly - /// writes a time-only value that way. - /// - /// a date string that is ISO 8601 conform. - /// Returns a Calendar. - /// Is thrown when the string is non-conform. - public static XMPDateTime Parse(string iso8601String) - { - return Parse(iso8601String, new XMPDateTimeImpl()); - } - - /// a date string that is ISO 8601 conform. - /// an existing XMPDateTime to set with the parsed date - /// Returns an XMPDateTime-object containing the ISO8601-date. - /// Is thrown when the string is non-conform. - public static XMPDateTime Parse(string iso8601String, XMPDateTime binValue) - { - if (iso8601String == null) - { - throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); - } - else - { - if (iso8601String.Length == 0) - { - return binValue; - } - } - ParseState input = new ParseState(iso8601String); - int value; - if (input.Ch(0) == '-') - { - input.Skip(); - } - // Extract the year. - value = input.GatherInt("Invalid year in date string", 9999); - if (input.HasNext() && input.Ch() != '-') - { - throw new XMPException("Invalid date string, after year", XMPErrorConstants.Badvalue); - } - if (input.Ch(0) == '-') - { - value = -value; - } - binValue.SetYear(value); - if (!input.HasNext()) - { - return binValue; - } - input.Skip(); - // Extract the month. - value = input.GatherInt("Invalid month in date string", 12); - if (input.HasNext() && input.Ch() != '-') - { - throw new XMPException("Invalid date string, after month", XMPErrorConstants.Badvalue); - } - binValue.SetMonth(value); - if (!input.HasNext()) - { - return binValue; - } - input.Skip(); - // Extract the day. - value = input.GatherInt("Invalid day in date string", 31); - if (input.HasNext() && input.Ch() != 'T') - { - throw new XMPException("Invalid date string, after day", XMPErrorConstants.Badvalue); - } - binValue.SetDay(value); - if (!input.HasNext()) - { - return binValue; - } - input.Skip(); - // Extract the hour. - value = input.GatherInt("Invalid hour in date string", 23); - binValue.SetHour(value); - if (!input.HasNext()) - { - return binValue; - } - // Extract the minute. - if (input.Ch() == ':') - { - input.Skip(); - value = input.GatherInt("Invalid minute in date string", 59); - if (input.HasNext() && input.Ch() != ':' && input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-') - { - throw new XMPException("Invalid date string, after minute", XMPErrorConstants.Badvalue); - } - binValue.SetMinute(value); - } - if (!input.HasNext()) - { - return binValue; - } - else - { - if (input.HasNext() && input.Ch() == ':') - { - input.Skip(); - value = input.GatherInt("Invalid whole seconds in date string", 59); - if (input.HasNext() && input.Ch() != '.' && input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-') - { - throw new XMPException("Invalid date string, after whole seconds", XMPErrorConstants.Badvalue); - } - binValue.SetSecond(value); - if (input.Ch() == '.') - { - input.Skip(); - int digits = input.Pos(); - value = input.GatherInt("Invalid fractional seconds in date string", 999999999); - if (input.HasNext() && (input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-')) - { - throw new XMPException("Invalid date string, after fractional second", XMPErrorConstants.Badvalue); - } - digits = input.Pos() - digits; - for (; digits > 9; --digits) - { - value = value / 10; - } - for (; digits < 9; ++digits) - { - value = value * 10; - } - binValue.SetNanoSecond(value); - } - } - else - { - if (input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-') - { - throw new XMPException("Invalid date string, after time", XMPErrorConstants.Badvalue); - } - } - } - int tzSign = 0; - int tzHour = 0; - int tzMinute = 0; - if (!input.HasNext()) - { - // no Timezone at all - return binValue; - } - else - { - if (input.Ch() == 'Z') - { - input.Skip(); - } - else - { - if (input.HasNext()) - { - if (input.Ch() == '+') - { - tzSign = 1; - } - else - { - if (input.Ch() == '-') - { - tzSign = -1; - } - else - { - throw new XMPException("Time zone must begin with 'Z', '+', or '-'", XMPErrorConstants.Badvalue); - } - } - input.Skip(); - // Extract the time zone hour. - tzHour = input.GatherInt("Invalid time zone hour in date string", 23); - if (input.HasNext()) - { - if (input.Ch() == ':') - { - input.Skip(); - // Extract the time zone minute. - tzMinute = input.GatherInt("Invalid time zone minute in date string", 59); - } - else - { - throw new XMPException("Invalid date string, after time zone hour", XMPErrorConstants.Badvalue); - } - } - } - } - } - // create a corresponding TZ and set it time zone - int offset = (tzHour * 3600 * 1000 + tzMinute * 60 * 1000) * tzSign; - binValue.SetTimeZone((TimeZoneInfo)new SimpleTimeZone(offset, string.Empty)); - if (input.HasNext()) - { - throw new XMPException("Invalid date string, extra chars at end", XMPErrorConstants.Badvalue); - } - return binValue; - } - - ///

Converts a Calendar into an ISO 8601 string. - /// - /// Converts a Calendar into an ISO 8601 string. - /// Format a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime: - ///
    - ///
  • YYYY - ///
  • YYYY-MM - ///
  • YYYY-MM-DD - ///
  • YYYY-MM-DDThh:mmTZD - ///
  • YYYY-MM-DDThh:mm:ssTZD - ///
  • YYYY-MM-DDThh:mm:ss.sTZD - ///
- /// Data fields: - ///
    - ///
  • YYYY = four-digit year - ///
  • MM = two-digit month (01=January, etc.) - ///
  • DD = two-digit day of month (01 through 31) - ///
  • hh = two digits of hour (00 through 23) - ///
  • mm = two digits of minute (00 through 59) - ///
  • ss = two digits of second (00 through 59) - ///
  • s = one or more digits representing a decimal fraction of a second - ///
  • TZD = time zone designator (Z or +hh:mm or -hh:mm) - ///
- ///

- /// Note: ISO 8601 does not seem to allow years less than 1000 or greater than 9999. - /// We allow any year, even negative ones. The year is formatted as "%.4d".

- /// Note: Fix for bug 1269463 (silently fix out of range values) included in parsing. - /// The quasi-bogus "time only" values from Photoshop CS are not supported. - /// - /// an XMPDateTime-object. - /// Returns an ISO 8601 string. - public static string Render(XMPDateTime dateTime) - { - StringBuilder buffer = new StringBuilder(); - if (dateTime.HasDate()) - { - // year is rendered in any case, even 0000 - DecimalFormat df = new DecimalFormat("0000", new DecimalFormatSymbols(Extensions.GetEnglishCulture())); - buffer.Append(df.Format(dateTime.GetYear())); - if (dateTime.GetMonth() == 0) - { - return buffer.ToString(); - } - // month - df.ApplyPattern("'-'00"); - buffer.Append(df.Format(dateTime.GetMonth())); - if (dateTime.GetDay() == 0) - { - return buffer.ToString(); - } - // day - buffer.Append(df.Format(dateTime.GetDay())); - // time, rendered if any time field is not zero - if (dateTime.HasTime()) - { - // hours and minutes - buffer.Append('T'); - df.ApplyPattern("00"); - buffer.Append(df.Format(dateTime.GetHour())); - buffer.Append(':'); - buffer.Append(df.Format(dateTime.GetMinute())); - // seconds and nanoseconds - if (dateTime.GetSecond() != 0 || dateTime.GetNanoSecond() != 0) - { - double seconds = dateTime.GetSecond() + dateTime.GetNanoSecond() / 1e9d; - df.ApplyPattern(":00.#########"); - buffer.Append(df.Format(seconds)); - } - // time zone - if (dateTime.HasTimeZone()) - { - // used to calculate the time zone offset incl. Daylight Savings - long timeInMillis = dateTime.GetCalendar().GetTimeInMillis(); - int offset = dateTime.GetTimeZone().GetOffset(timeInMillis); - if (offset == 0) - { - // UTC - buffer.Append('Z'); - } - else - { - int thours = offset / 3600000; - int tminutes = Math.Abs(offset % 3600000 / 60000); - df.ApplyPattern("+00;-00"); - buffer.Append(df.Format(thours)); - df.ApplyPattern(":00"); - buffer.Append(df.Format(tminutes)); - } - } - } - } - return buffer.ToString(); - } - } - - /// 22.08.2006 - internal class ParseState - { - private string str; - - private int pos = 0; - - /// initializes the parser container - public ParseState(string str) - { - this.str = str; - } - - /// Returns the length of the input. - public virtual int Length() - { - return str.Length; - } - - /// Returns whether there are more chars to come. - public virtual bool HasNext() - { - return pos < str.Length; - } - - /// index of char - /// Returns char at a certain index. - public virtual char Ch(int index) - { - return index < str.Length ? str[index] : (char)0x0000; - } - - /// Returns the current char or 0x0000 if there are no more chars. - public virtual char Ch() - { - return pos < str.Length ? str[pos] : (char)0x0000; - } - - ///

Skips the next char. - public virtual void Skip() - { - pos++; - } - - /// Returns the current position. - public virtual int Pos() - { - return pos; - } - - /// Parses a integer from the source and sets the pointer after it. - /// Error message to put in the exception if no number can be found - /// the max value of the number to return - /// Returns the parsed integer. - /// Thrown if no integer can be found. - public virtual int GatherInt(string errorMsg, int maxValue) - { - int value = 0; - bool success = false; - char ch = Ch(pos); - while ('0' <= ch && ch <= '9') - { - value = (value * 10) + (ch - '0'); - success = true; - pos++; - ch = Ch(pos); - } - if (success) - { - if (value > maxValue) - { - return maxValue; - } - else - { - if (value < 0) - { - return 0; - } - else - { - return value; - } - } - } - else - { - throw new XMPException(errorMsg, XMPErrorConstants.Badvalue); - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using System.Text; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// Converts between ISO 8601 Strings and Calendar with millisecond resolution. + /// 16.02.2006 + public sealed class ISO8601Converter + { + /// Hides public constructor + private ISO8601Converter() + { + } + + // EMPTY + /// Converts an ISO 8601 string to an XMPDateTime. + /// + /// Converts an ISO 8601 string to an XMPDateTime. + /// Parse a date according to ISO 8601 and + /// http://www.w3.org/TR/NOTE-datetime: + ///
    + ///
  • YYYY + ///
  • YYYY-MM + ///
  • YYYY-MM-DD + ///
  • YYYY-MM-DDThh:mmTZD + ///
  • YYYY-MM-DDThh:mm:ssTZD + ///
  • YYYY-MM-DDThh:mm:ss.sTZD + ///
+ /// Data fields: + ///
    + ///
  • YYYY = four-digit year + ///
  • MM = two-digit month (01=January, etc.) + ///
  • DD = two-digit day of month (01 through 31) + ///
  • hh = two digits of hour (00 through 23) + ///
  • mm = two digits of minute (00 through 59) + ///
  • ss = two digits of second (00 through 59) + ///
  • s = one or more digits representing a decimal fraction of a second + ///
  • TZD = time zone designator (Z or +hh:mm or -hh:mm) + ///
+ /// Note that ISO 8601 does not seem to allow years less than 1000 or greater + /// than 9999. We allow any year, even negative ones. The year is formatted + /// as "%.4d". + ///

+ /// Note: Tolerate missing TZD, assume is UTC. Photoshop 8 writes + /// dates like this for exif:GPSTimeStamp.
+ /// Note: DOES NOT APPLY ANYMORE. + /// Tolerate missing date portion, in case someone foolishly + /// writes a time-only value that way. + /// + /// a date string that is ISO 8601 conform. + /// Returns a Calendar. + /// Is thrown when the string is non-conform. + public static XMPDateTime Parse(string iso8601String) + { + return Parse(iso8601String, new XMPDateTimeImpl()); + } + + /// a date string that is ISO 8601 conform. + /// an existing XMPDateTime to set with the parsed date + /// Returns an XMPDateTime-object containing the ISO8601-date. + /// Is thrown when the string is non-conform. + public static XMPDateTime Parse(string iso8601String, XMPDateTime binValue) + { + if (iso8601String == null) + { + throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); + } + else + { + if (iso8601String.Length == 0) + { + return binValue; + } + } + ParseState input = new ParseState(iso8601String); + int value; + if (input.Ch(0) == '-') + { + input.Skip(); + } + // Extract the year. + value = input.GatherInt("Invalid year in date string", 9999); + if (input.HasNext() && input.Ch() != '-') + { + throw new XMPException("Invalid date string, after year", XMPErrorConstants.Badvalue); + } + if (input.Ch(0) == '-') + { + value = -value; + } + binValue.SetYear(value); + if (!input.HasNext()) + { + return binValue; + } + input.Skip(); + // Extract the month. + value = input.GatherInt("Invalid month in date string", 12); + if (input.HasNext() && input.Ch() != '-') + { + throw new XMPException("Invalid date string, after month", XMPErrorConstants.Badvalue); + } + binValue.SetMonth(value); + if (!input.HasNext()) + { + return binValue; + } + input.Skip(); + // Extract the day. + value = input.GatherInt("Invalid day in date string", 31); + if (input.HasNext() && input.Ch() != 'T') + { + throw new XMPException("Invalid date string, after day", XMPErrorConstants.Badvalue); + } + binValue.SetDay(value); + if (!input.HasNext()) + { + return binValue; + } + input.Skip(); + // Extract the hour. + value = input.GatherInt("Invalid hour in date string", 23); + binValue.SetHour(value); + if (!input.HasNext()) + { + return binValue; + } + // Extract the minute. + if (input.Ch() == ':') + { + input.Skip(); + value = input.GatherInt("Invalid minute in date string", 59); + if (input.HasNext() && input.Ch() != ':' && input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-') + { + throw new XMPException("Invalid date string, after minute", XMPErrorConstants.Badvalue); + } + binValue.SetMinute(value); + } + if (!input.HasNext()) + { + return binValue; + } + else + { + if (input.HasNext() && input.Ch() == ':') + { + input.Skip(); + value = input.GatherInt("Invalid whole seconds in date string", 59); + if (input.HasNext() && input.Ch() != '.' && input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-') + { + throw new XMPException("Invalid date string, after whole seconds", XMPErrorConstants.Badvalue); + } + binValue.SetSecond(value); + if (input.Ch() == '.') + { + input.Skip(); + int digits = input.Pos(); + value = input.GatherInt("Invalid fractional seconds in date string", 999999999); + if (input.HasNext() && (input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-')) + { + throw new XMPException("Invalid date string, after fractional second", XMPErrorConstants.Badvalue); + } + digits = input.Pos() - digits; + for (; digits > 9; --digits) + { + value = value / 10; + } + for (; digits < 9; ++digits) + { + value = value * 10; + } + binValue.SetNanoSecond(value); + } + } + else + { + if (input.Ch() != 'Z' && input.Ch() != '+' && input.Ch() != '-') + { + throw new XMPException("Invalid date string, after time", XMPErrorConstants.Badvalue); + } + } + } + int tzSign = 0; + int tzHour = 0; + int tzMinute = 0; + if (!input.HasNext()) + { + // no Timezone at all + return binValue; + } + else + { + if (input.Ch() == 'Z') + { + input.Skip(); + } + else + { + if (input.HasNext()) + { + if (input.Ch() == '+') + { + tzSign = 1; + } + else + { + if (input.Ch() == '-') + { + tzSign = -1; + } + else + { + throw new XMPException("Time zone must begin with 'Z', '+', or '-'", XMPErrorConstants.Badvalue); + } + } + input.Skip(); + // Extract the time zone hour. + tzHour = input.GatherInt("Invalid time zone hour in date string", 23); + if (input.HasNext()) + { + if (input.Ch() == ':') + { + input.Skip(); + // Extract the time zone minute. + tzMinute = input.GatherInt("Invalid time zone minute in date string", 59); + } + else + { + throw new XMPException("Invalid date string, after time zone hour", XMPErrorConstants.Badvalue); + } + } + } + } + } + // create a corresponding TZ and set it time zone + int offset = (tzHour * 3600 * 1000 + tzMinute * 60 * 1000) * tzSign; + binValue.SetTimeZone((TimeZoneInfo)new SimpleTimeZone(offset, string.Empty)); + if (input.HasNext()) + { + throw new XMPException("Invalid date string, extra chars at end", XMPErrorConstants.Badvalue); + } + return binValue; + } + + ///

Converts a Calendar into an ISO 8601 string. + /// + /// Converts a Calendar into an ISO 8601 string. + /// Format a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime: + ///
    + ///
  • YYYY + ///
  • YYYY-MM + ///
  • YYYY-MM-DD + ///
  • YYYY-MM-DDThh:mmTZD + ///
  • YYYY-MM-DDThh:mm:ssTZD + ///
  • YYYY-MM-DDThh:mm:ss.sTZD + ///
+ /// Data fields: + ///
    + ///
  • YYYY = four-digit year + ///
  • MM = two-digit month (01=January, etc.) + ///
  • DD = two-digit day of month (01 through 31) + ///
  • hh = two digits of hour (00 through 23) + ///
  • mm = two digits of minute (00 through 59) + ///
  • ss = two digits of second (00 through 59) + ///
  • s = one or more digits representing a decimal fraction of a second + ///
  • TZD = time zone designator (Z or +hh:mm or -hh:mm) + ///
+ ///

+ /// Note: ISO 8601 does not seem to allow years less than 1000 or greater than 9999. + /// We allow any year, even negative ones. The year is formatted as "%.4d".

+ /// Note: Fix for bug 1269463 (silently fix out of range values) included in parsing. + /// The quasi-bogus "time only" values from Photoshop CS are not supported. + /// + /// an XMPDateTime-object. + /// Returns an ISO 8601 string. + public static string Render(XMPDateTime dateTime) + { + StringBuilder buffer = new StringBuilder(); + if (dateTime.HasDate()) + { + // year is rendered in any case, even 0000 + DecimalFormat df = new DecimalFormat("0000", new DecimalFormatSymbols(Extensions.GetEnglishCulture())); + buffer.Append(df.Format(dateTime.GetYear())); + if (dateTime.GetMonth() == 0) + { + return buffer.ToString(); + } + // month + df.ApplyPattern("'-'00"); + buffer.Append(df.Format(dateTime.GetMonth())); + if (dateTime.GetDay() == 0) + { + return buffer.ToString(); + } + // day + buffer.Append(df.Format(dateTime.GetDay())); + // time, rendered if any time field is not zero + if (dateTime.HasTime()) + { + // hours and minutes + buffer.Append('T'); + df.ApplyPattern("00"); + buffer.Append(df.Format(dateTime.GetHour())); + buffer.Append(':'); + buffer.Append(df.Format(dateTime.GetMinute())); + // seconds and nanoseconds + if (dateTime.GetSecond() != 0 || dateTime.GetNanoSecond() != 0) + { + double seconds = dateTime.GetSecond() + dateTime.GetNanoSecond() / 1e9d; + df.ApplyPattern(":00.#########"); + buffer.Append(df.Format(seconds)); + } + // time zone + if (dateTime.HasTimeZone()) + { + // used to calculate the time zone offset incl. Daylight Savings + long timeInMillis = dateTime.GetCalendar().GetTimeInMillis(); + int offset = dateTime.GetTimeZone().GetOffset(timeInMillis); + if (offset == 0) + { + // UTC + buffer.Append('Z'); + } + else + { + int thours = offset / 3600000; + int tminutes = Math.Abs(offset % 3600000 / 60000); + df.ApplyPattern("+00;-00"); + buffer.Append(df.Format(thours)); + df.ApplyPattern(":00"); + buffer.Append(df.Format(tminutes)); + } + } + } + } + return buffer.ToString(); + } + } + + /// 22.08.2006 + internal class ParseState + { + private string str; + + private int pos = 0; + + /// initializes the parser container + public ParseState(string str) + { + this.str = str; + } + + /// Returns the length of the input. + public virtual int Length() + { + return str.Length; + } + + /// Returns whether there are more chars to come. + public virtual bool HasNext() + { + return pos < str.Length; + } + + /// index of char + /// Returns char at a certain index. + public virtual char Ch(int index) + { + return index < str.Length ? str[index] : (char)0x0000; + } + + /// Returns the current char or 0x0000 if there are no more chars. + public virtual char Ch() + { + return pos < str.Length ? str[pos] : (char)0x0000; + } + + ///

Skips the next char. + public virtual void Skip() + { + pos++; + } + + /// Returns the current position. + public virtual int Pos() + { + return pos; + } + + /// Parses a integer from the source and sets the pointer after it. + /// Error message to put in the exception if no number can be found + /// the max value of the number to return + /// Returns the parsed integer. + /// Thrown if no integer can be found. + public virtual int GatherInt(string errorMsg, int maxValue) + { + int value = 0; + bool success = false; + char ch = Ch(pos); + while ('0' <= ch && ch <= '9') + { + value = (value * 10) + (ch - '0'); + success = true; + pos++; + ch = Ch(pos); + } + if (success) + { + if (value > maxValue) + { + return maxValue; + } + else + { + if (value < 0) + { + return 0; + } + else + { + return value; + } + } + } + else + { + throw new XMPException(errorMsg, XMPErrorConstants.Badvalue); + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/Latin1Converter.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/Latin1Converter.cs index 242deda1d..2ccf19e25 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/Latin1Converter.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/Latin1Converter.cs @@ -1,189 +1,189 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// 12.10.2006 - public class Latin1Converter - { - private const int StateStart = 0; - - private const int StateUtf8char = 11; - - /// Private constructor - private Latin1Converter() - { - } - - // EMPTY - /// A converter that processes a byte buffer containing a mix of UTF8 and Latin-1/Cp1252 chars. - /// - /// A converter that processes a byte buffer containing a mix of UTF8 and Latin-1/Cp1252 chars. - /// The result is a buffer where those chars have been converted to UTF-8; - /// that means it contains only valid UTF-8 chars. - ///

- /// Explanation of the processing: First the encoding of the buffer is detected looking - /// at the first four bytes (that works only if the buffer starts with an ASCII-char, - /// like xmls '<'). UTF-16/32 flavours do not require further proccessing. - ///

- /// In the case, UTF-8 is detected, it assumes wrong UTF8 chars to be a sequence of - /// Latin-1/Cp1252 encoded bytes and converts the chars to their corresponding UTF-8 byte - /// sequence. - ///

- /// The 0x80..0x9F range is undefined in Latin-1, but is defined in Windows code - /// page 1252. The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are formally undefined - /// by Windows 1252. These are in XML's RestrictedChar set, so we map them to a - /// space. - ///

- /// The official Latin-1 characters in the range 0xA0..0xFF are converted into - /// the Unicode Latin Supplement range U+00A0 - U+00FF. - ///

- /// Example: If an Euro-symbol (€) appears in the byte buffer (0xE2, 0x82, 0xAC), - /// it will be left as is. But if only the first two bytes are appearing, - /// followed by an ASCII char a (0xE2 - 0x82 - 0x41), it will be converted to - /// 0xC3, 0xA2 (â) - 0xE2, 0x80, 0x9A (‚) - 0x41 (a). - /// - /// a byte buffer contain - /// Returns a new buffer containing valid UTF-8 - public static ByteBuffer Convert(ByteBuffer buffer) - { - if ("UTF-8".Equals(buffer.GetEncoding())) - { - // the buffer containing one UTF-8 char (up to 8 bytes) - sbyte[] readAheadBuffer = new sbyte[8]; - // the number of bytes read ahead. - int readAhead = 0; - // expected UTF8 bytesto come - int expectedBytes = 0; - // output buffer with estimated length - ByteBuffer @out = new ByteBuffer(buffer.Length() * 4 / 3); - int state = StateStart; - for (int i = 0; i < buffer.Length(); i++) - { - int b = buffer.CharAt(i); - switch (state) - { - case StateStart: - default: - { - if (b < unchecked((int)(0x7F))) - { - @out.Append(unchecked((sbyte)b)); - } - else - { - if (b >= unchecked((int)(0xC0))) - { - // start of UTF8 sequence - expectedBytes = -1; - int test = b; - for (; expectedBytes < 8 && (test & unchecked((int)(0x80))) == unchecked((int)(0x80)); test = test << 1) - { - expectedBytes++; - } - readAheadBuffer[readAhead++] = unchecked((sbyte)b); - state = StateUtf8char; - } - else - { - // implicitly: b >= 0x80 && b < 0xC0 - // invalid UTF8 start char, assume to be Latin-1 - sbyte[] utf8 = ConvertToUTF8(unchecked((sbyte)b)); - @out.Append(utf8); - } - } - break; - } - - case StateUtf8char: - { - if (expectedBytes > 0 && (b & unchecked((int)(0xC0))) == unchecked((int)(0x80))) - { - // valid UTF8 char, add to readAheadBuffer - readAheadBuffer[readAhead++] = unchecked((sbyte)b); - expectedBytes--; - if (expectedBytes == 0) - { - @out.Append(readAheadBuffer, 0, readAhead); - readAhead = 0; - state = StateStart; - } - } - else - { - // invalid UTF8 char: - // 1. convert first of seq to UTF8 - sbyte[] utf8 = ConvertToUTF8(readAheadBuffer[0]); - @out.Append(utf8); - // 2. continue processing at second byte of sequence - i = i - readAhead; - readAhead = 0; - state = StateStart; - } - break; - } - } - } - // loop ends with "half" Utf8 char --> assume that the bytes are Latin-1 - if (state == StateUtf8char) - { - for (int j = 0; j < readAhead; j++) - { - sbyte b = readAheadBuffer[j]; - sbyte[] utf8 = ConvertToUTF8(b); - @out.Append(utf8); - } - } - return @out; - } - else - { - // Latin-1 fixing applies only to UTF-8 - return buffer; - } - } - - ///

- /// Converts a Cp1252 char (contains all Latin-1 chars above 0x80) into a - /// UTF-8 byte sequence. - /// - /// - /// Converts a Cp1252 char (contains all Latin-1 chars above 0x80) into a - /// UTF-8 byte sequence. The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are - /// formally undefined by Windows 1252 and therefore replaced by a space - /// (0x20). - /// - /// an Cp1252 / Latin-1 byte - /// Returns a byte array containing a UTF-8 byte sequence. - private static sbyte[] ConvertToUTF8(sbyte ch) - { - int c = ch & unchecked((int)(0xFF)); - try - { - if (c >= unchecked((int)(0x80))) - { - if (c == unchecked((int)(0x81)) || c == unchecked((int)(0x8D)) || c == unchecked((int)(0x8F)) || c == unchecked((int)(0x90)) || c == unchecked((int)(0x9D))) - { - return new sbyte[] { unchecked((int)(0x20)) }; - } - // space for undefined - // interpret byte as Windows Cp1252 char - return Runtime.GetBytesForString(Runtime.GetStringForBytes(new sbyte[] { ch }, "cp1252"), "UTF-8"); - } - } - catch (UnsupportedEncodingException) - { - } - // EMPTY - return new sbyte[] { ch }; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// 12.10.2006 + public class Latin1Converter + { + private const int StateStart = 0; + + private const int StateUtf8char = 11; + + /// Private constructor + private Latin1Converter() + { + } + + // EMPTY + /// A converter that processes a byte buffer containing a mix of UTF8 and Latin-1/Cp1252 chars. + /// + /// A converter that processes a byte buffer containing a mix of UTF8 and Latin-1/Cp1252 chars. + /// The result is a buffer where those chars have been converted to UTF-8; + /// that means it contains only valid UTF-8 chars. + ///

+ /// Explanation of the processing: First the encoding of the buffer is detected looking + /// at the first four bytes (that works only if the buffer starts with an ASCII-char, + /// like xmls '<'). UTF-16/32 flavours do not require further proccessing. + ///

+ /// In the case, UTF-8 is detected, it assumes wrong UTF8 chars to be a sequence of + /// Latin-1/Cp1252 encoded bytes and converts the chars to their corresponding UTF-8 byte + /// sequence. + ///

+ /// The 0x80..0x9F range is undefined in Latin-1, but is defined in Windows code + /// page 1252. The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are formally undefined + /// by Windows 1252. These are in XML's RestrictedChar set, so we map them to a + /// space. + ///

+ /// The official Latin-1 characters in the range 0xA0..0xFF are converted into + /// the Unicode Latin Supplement range U+00A0 - U+00FF. + ///

+ /// Example: If an Euro-symbol (€) appears in the byte buffer (0xE2, 0x82, 0xAC), + /// it will be left as is. But if only the first two bytes are appearing, + /// followed by an ASCII char a (0xE2 - 0x82 - 0x41), it will be converted to + /// 0xC3, 0xA2 (â) - 0xE2, 0x80, 0x9A (‚) - 0x41 (a). + /// + /// a byte buffer contain + /// Returns a new buffer containing valid UTF-8 + public static ByteBuffer Convert(ByteBuffer buffer) + { + if ("UTF-8".Equals(buffer.GetEncoding())) + { + // the buffer containing one UTF-8 char (up to 8 bytes) + sbyte[] readAheadBuffer = new sbyte[8]; + // the number of bytes read ahead. + int readAhead = 0; + // expected UTF8 bytesto come + int expectedBytes = 0; + // output buffer with estimated length + ByteBuffer @out = new ByteBuffer(buffer.Length() * 4 / 3); + int state = StateStart; + for (int i = 0; i < buffer.Length(); i++) + { + int b = buffer.CharAt(i); + switch (state) + { + case StateStart: + default: + { + if (b < unchecked((int)(0x7F))) + { + @out.Append(unchecked((sbyte)b)); + } + else + { + if (b >= unchecked((int)(0xC0))) + { + // start of UTF8 sequence + expectedBytes = -1; + int test = b; + for (; expectedBytes < 8 && (test & unchecked((int)(0x80))) == unchecked((int)(0x80)); test = test << 1) + { + expectedBytes++; + } + readAheadBuffer[readAhead++] = unchecked((sbyte)b); + state = StateUtf8char; + } + else + { + // implicitly: b >= 0x80 && b < 0xC0 + // invalid UTF8 start char, assume to be Latin-1 + sbyte[] utf8 = ConvertToUTF8(unchecked((sbyte)b)); + @out.Append(utf8); + } + } + break; + } + + case StateUtf8char: + { + if (expectedBytes > 0 && (b & unchecked((int)(0xC0))) == unchecked((int)(0x80))) + { + // valid UTF8 char, add to readAheadBuffer + readAheadBuffer[readAhead++] = unchecked((sbyte)b); + expectedBytes--; + if (expectedBytes == 0) + { + @out.Append(readAheadBuffer, 0, readAhead); + readAhead = 0; + state = StateStart; + } + } + else + { + // invalid UTF8 char: + // 1. convert first of seq to UTF8 + sbyte[] utf8 = ConvertToUTF8(readAheadBuffer[0]); + @out.Append(utf8); + // 2. continue processing at second byte of sequence + i = i - readAhead; + readAhead = 0; + state = StateStart; + } + break; + } + } + } + // loop ends with "half" Utf8 char --> assume that the bytes are Latin-1 + if (state == StateUtf8char) + { + for (int j = 0; j < readAhead; j++) + { + sbyte b = readAheadBuffer[j]; + sbyte[] utf8 = ConvertToUTF8(b); + @out.Append(utf8); + } + } + return @out; + } + else + { + // Latin-1 fixing applies only to UTF-8 + return buffer; + } + } + + ///

+ /// Converts a Cp1252 char (contains all Latin-1 chars above 0x80) into a + /// UTF-8 byte sequence. + /// + /// + /// Converts a Cp1252 char (contains all Latin-1 chars above 0x80) into a + /// UTF-8 byte sequence. The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are + /// formally undefined by Windows 1252 and therefore replaced by a space + /// (0x20). + /// + /// an Cp1252 / Latin-1 byte + /// Returns a byte array containing a UTF-8 byte sequence. + private static sbyte[] ConvertToUTF8(sbyte ch) + { + int c = ch & unchecked((int)(0xFF)); + try + { + if (c >= unchecked((int)(0x80))) + { + if (c == unchecked((int)(0x81)) || c == unchecked((int)(0x8D)) || c == unchecked((int)(0x8F)) || c == unchecked((int)(0x90)) || c == unchecked((int)(0x9D))) + { + return new sbyte[] { unchecked((int)(0x20)) }; + } + // space for undefined + // interpret byte as Windows Cp1252 char + return Runtime.GetBytesForString(Runtime.GetStringForBytes(new sbyte[] { ch }, "cp1252"), "UTF-8"); + } + } + catch (UnsupportedEncodingException) + { + } + // EMPTY + return new sbyte[] { ch }; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParameterAsserts.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParameterAsserts.cs index bbe85d31f..d0f07bdea 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParameterAsserts.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParameterAsserts.cs @@ -1,128 +1,128 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -namespace Com.Adobe.Xmp.Impl -{ - /// 11.08.2006 - internal class ParameterAsserts : XMPConst - { - /// private constructor - private ParameterAsserts() - { - } - - // EMPTY - /// Asserts that an array name is set. - /// an array name - /// Array name is null or empty - public static void AssertArrayName(string arrayName) - { - if (arrayName == null || arrayName.Length == 0) - { - throw new XMPException("Empty array name", XMPErrorConstants.Badparam); - } - } - - /// Asserts that a property name is set. - /// a property name or path - /// Property name is null or empty - public static void AssertPropName(string propName) - { - if (propName == null || propName.Length == 0) - { - throw new XMPException("Empty property name", XMPErrorConstants.Badparam); - } - } - - /// Asserts that a schema namespace is set. - /// a schema namespace - /// Schema is null or empty - public static void AssertSchemaNS(string schemaNS) - { - if (schemaNS == null || schemaNS.Length == 0) - { - throw new XMPException("Empty schema namespace URI", XMPErrorConstants.Badparam); - } - } - - /// Asserts that a prefix is set. - /// a prefix - /// Prefix is null or empty - public static void AssertPrefix(string prefix) - { - if (prefix == null || prefix.Length == 0) - { - throw new XMPException("Empty prefix", XMPErrorConstants.Badparam); - } - } - - /// Asserts that a specific language is set. - /// a specific lang - /// Specific language is null or empty - public static void AssertSpecificLang(string specificLang) - { - if (specificLang == null || specificLang.Length == 0) - { - throw new XMPException("Empty specific language", XMPErrorConstants.Badparam); - } - } - - /// Asserts that a struct name is set. - /// a struct name - /// Struct name is null or empty - public static void AssertStructName(string structName) - { - if (structName == null || structName.Length == 0) - { - throw new XMPException("Empty array name", XMPErrorConstants.Badparam); - } - } - - /// Asserts that any string parameter is set. - /// any string parameter - /// Thrown if the parameter is null or has length 0. - public static void AssertNotNull(object param) - { - if (param == null) - { - throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); - } - else - { - if ((param is string) && ((string)param).Length == 0) - { - throw new XMPException("Parameter must not be null or empty", XMPErrorConstants.Badparam); - } - } - } - - /// - /// Asserts that the xmp object is of this implemention - /// ( - /// - /// ). - /// - /// the XMP object - /// A wrong implentaion is used. - public static void AssertImplementation(XMPMeta xmp) - { - if (xmp == null) - { - throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); - } - else - { - if (!(xmp is XMPMetaImpl)) - { - throw new XMPException("The XMPMeta-object is not compatible with this implementation", XMPErrorConstants.Badparam); - } - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +namespace Com.Adobe.Xmp.Impl +{ + /// 11.08.2006 + internal class ParameterAsserts : XMPConst + { + /// private constructor + private ParameterAsserts() + { + } + + // EMPTY + /// Asserts that an array name is set. + /// an array name + /// Array name is null or empty + public static void AssertArrayName(string arrayName) + { + if (arrayName == null || arrayName.Length == 0) + { + throw new XMPException("Empty array name", XMPErrorConstants.Badparam); + } + } + + /// Asserts that a property name is set. + /// a property name or path + /// Property name is null or empty + public static void AssertPropName(string propName) + { + if (propName == null || propName.Length == 0) + { + throw new XMPException("Empty property name", XMPErrorConstants.Badparam); + } + } + + /// Asserts that a schema namespace is set. + /// a schema namespace + /// Schema is null or empty + public static void AssertSchemaNS(string schemaNS) + { + if (schemaNS == null || schemaNS.Length == 0) + { + throw new XMPException("Empty schema namespace URI", XMPErrorConstants.Badparam); + } + } + + /// Asserts that a prefix is set. + /// a prefix + /// Prefix is null or empty + public static void AssertPrefix(string prefix) + { + if (prefix == null || prefix.Length == 0) + { + throw new XMPException("Empty prefix", XMPErrorConstants.Badparam); + } + } + + /// Asserts that a specific language is set. + /// a specific lang + /// Specific language is null or empty + public static void AssertSpecificLang(string specificLang) + { + if (specificLang == null || specificLang.Length == 0) + { + throw new XMPException("Empty specific language", XMPErrorConstants.Badparam); + } + } + + /// Asserts that a struct name is set. + /// a struct name + /// Struct name is null or empty + public static void AssertStructName(string structName) + { + if (structName == null || structName.Length == 0) + { + throw new XMPException("Empty array name", XMPErrorConstants.Badparam); + } + } + + /// Asserts that any string parameter is set. + /// any string parameter + /// Thrown if the parameter is null or has length 0. + public static void AssertNotNull(object param) + { + if (param == null) + { + throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); + } + else + { + if ((param is string) && ((string)param).Length == 0) + { + throw new XMPException("Parameter must not be null or empty", XMPErrorConstants.Badparam); + } + } + } + + /// + /// Asserts that the xmp object is of this implemention + /// ( + /// + /// ). + /// + /// the XMP object + /// A wrong implentaion is used. + public static void AssertImplementation(XMPMeta xmp) + { + if (xmp == null) + { + throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); + } + else + { + if (!(xmp is XMPMetaImpl)) + { + throw new XMPException("The XMPMeta-object is not compatible with this implementation", XMPErrorConstants.Badparam); + } + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParseRDF.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParseRDF.cs index 2fb8a294d..9e3487e18 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParseRDF.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/ParseRDF.cs @@ -1,1322 +1,1322 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Collections; -using System.Diagnostics; -using System.Xml; -using Com.Adobe.Xmp.Options; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// Parser for "normal" XML serialisation of RDF. - /// 14.07.2006 - public class ParseRDF : XMPError, XMPConst - { - public const int RdftermOther = 0; - - /// Start of coreSyntaxTerms. - public const int RdftermRdf = 1; - - public const int RdftermId = 2; - - public const int RdftermAbout = 3; - - public const int RdftermParseType = 4; - - public const int RdftermResource = 5; - - public const int RdftermNodeId = 6; - - /// End of coreSyntaxTerms - public const int RdftermDatatype = 7; - - /// Start of additions for syntax Terms. - public const int RdftermDescription = 8; - - /// End of of additions for syntaxTerms. - public const int RdftermLi = 9; - - /// Start of oldTerms. - public const int RdftermAboutEach = 10; - - public const int RdftermAboutEachPrefix = 11; - - /// End of oldTerms. - public const int RdftermBagId = 12; - - public const int RdftermFirstCore = RdftermRdf; - - public const int RdftermLastCore = RdftermDatatype; - - /// ! Yes, the syntax terms include the core terms. - public const int RdftermFirstSyntax = RdftermFirstCore; - - public const int RdftermLastSyntax = RdftermLi; - - public const int RdftermFirstOld = RdftermAboutEach; - - public const int RdftermLastOld = RdftermBagId; - - /// this prefix is used for default namespaces - public const string DefaultPrefix = "_dflt"; - - /// The main parsing method. - /// - /// The main parsing method. The XML tree is walked through from the root node and and XMP tree - /// is created. This is a raw parse, the normalisation of the XMP tree happens outside. - /// - /// the XML root node - /// Returns an XMP metadata object (not normalized) - /// Occurs if the parsing fails for any reason. - internal static XMPMetaImpl Parse(XmlNode xmlRoot) - { - XMPMetaImpl xmp = new XMPMetaImpl(); - Rdf_RDF(xmp, xmlRoot); - return xmp; - } - - /// - /// Each of these parsing methods is responsible for recognizing an RDF - /// syntax production and adding the appropriate structure to the XMP tree. - /// - /// - /// Each of these parsing methods is responsible for recognizing an RDF - /// syntax production and adding the appropriate structure to the XMP tree. - /// They simply return for success, failures will throw an exception. - /// - /// the xmp metadata object that is generated - /// the top-level xml node - /// thown on parsing errors - internal static void Rdf_RDF(XMPMetaImpl xmp, XmlNode rdfRdfNode) - { - if (rdfRdfNode.HasAttributes()) - { - Rdf_NodeElementList(xmp, xmp.GetRoot(), rdfRdfNode); - } - else - { - throw new XMPException("Invalid attributes of rdf:RDF element", XMPErrorConstants.Badrdf); - } - } - - /// - /// 7.2.10 nodeElementList
- /// ws* ( nodeElement ws* ) - /// Note: this method is only called from the rdf:RDF-node (top level) - ///
- /// the xmp metadata object that is generated - /// the parent xmp node - /// the top-level xml node - /// thown on parsing errors - private static void Rdf_NodeElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode rdfRdfNode) - { - for (int i = 0; i < rdfRdfNode.ChildNodes.Count; i++) - { - XmlNode child = rdfRdfNode.ChildNodes.Item(i); - // filter whitespaces (and all text nodes) - if (!IsWhitespaceNode(child)) - { - Rdf_NodeElement(xmp, xmpParent, child, true); - } - } - } - - /// - /// 7.2.5 nodeElementURIs - /// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) - /// 7.2.11 nodeElement - /// start-element ( URI == nodeElementURIs, - /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) - /// propertyEltList - /// end-element() - /// A node element URI is rdf:Description or anything else that is not an RDF - /// term. - /// - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_NodeElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) - { - int nodeTerm = GetRDFTermKind(xmlNode); - if (nodeTerm != RdftermDescription && nodeTerm != RdftermOther) - { - throw new XMPException("Node element must be rdf:Description or typed node", XMPErrorConstants.Badrdf); - } - else - { - if (isTopLevel && nodeTerm == RdftermOther) - { - throw new XMPException("Top level typed node not allowed", XMPErrorConstants.Badxmp); - } - else - { - Rdf_NodeElementAttrs(xmp, xmpParent, xmlNode, isTopLevel); - Rdf_PropertyElementList(xmp, xmpParent, xmlNode, isTopLevel); - } - } - } - - /// - /// 7.2.7 propertyAttributeURIs - /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) - /// 7.2.11 nodeElement - /// start-element ( URI == nodeElementURIs, - /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) - /// propertyEltList - /// end-element() - /// Process the attribute list for an RDF node element. - /// - /// - /// 7.2.7 propertyAttributeURIs - /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) - /// 7.2.11 nodeElement - /// start-element ( URI == nodeElementURIs, - /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) - /// propertyEltList - /// end-element() - /// Process the attribute list for an RDF node element. A property attribute URI is - /// anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, - /// as are rdf:about attributes on inner nodes. - /// - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) - { - // Used to detect attributes that are mutually exclusive. - int exclusiveAttrs = 0; - for (int i = 0; i < xmlNode.Attributes.Count; i++) - { - XmlNode attribute = xmlNode.Attributes.Item(i); - // quick hack, ns declarations do not appear in C++ - // ignore "ID" without namespace - if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) - { - continue; - } - int attrTerm = GetRDFTermKind(attribute); - switch (attrTerm) - { - case RdftermId: - case RdftermNodeId: - case RdftermAbout: - { - if (exclusiveAttrs > 0) - { - throw new XMPException("Mutally exclusive about, ID, nodeID attributes", XMPErrorConstants.Badrdf); - } - exclusiveAttrs++; - if (isTopLevel && (attrTerm == RdftermAbout)) - { - // This is the rdf:about attribute on a top level node. Set - // the XMP tree name if - // it doesn't have a name yet. Make sure this name matches - // the XMP tree name. - if (xmpParent.GetName() != null && xmpParent.GetName().Length > 0) - { - if (!xmpParent.GetName().Equals(attribute.Value)) - { - throw new XMPException("Mismatched top level rdf:about values", XMPErrorConstants.Badxmp); - } - } - else - { - xmpParent.SetName(attribute.Value); - } - } - break; - } - - case RdftermOther: - { - AddChildNode(xmp, xmpParent, attribute, attribute.Value, isTopLevel); - break; - } - - default: - { - throw new XMPException("Invalid nodeElement attribute", XMPErrorConstants.Badrdf); - } - } - } - } - - /// - /// 7.2.13 propertyEltList - /// ws* ( propertyElt ws* ) - /// - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_PropertyElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlParent, bool isTopLevel) - { - for (int i = 0; i < xmlParent.ChildNodes.Count; i++) - { - XmlNode currChild = xmlParent.ChildNodes.Item(i); - if (IsWhitespaceNode(currChild)) - { - continue; - } - else - { - if (currChild.NodeType != XmlNodeType.Element) - { - throw new XMPException("Expected property element node not found", XMPErrorConstants.Badrdf); - } - else - { - Rdf_PropertyElement(xmp, xmpParent, currChild, isTopLevel); - } - } - } - } - - /// - /// 7.2.14 propertyElt - /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | - /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | - /// parseTypeOtherPropertyElt | emptyPropertyElt - /// 7.2.15 resourcePropertyElt - /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) - /// ws* nodeElement ws - /// end-element() - /// 7.2.16 literalPropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) - /// text() - /// end-element() - /// 7.2.17 parseTypeLiteralPropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) - /// literal - /// end-element() - /// 7.2.18 parseTypeResourcePropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) - /// propertyEltList - /// end-element() - /// 7.2.19 parseTypeCollectionPropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) - /// nodeElementList - /// end-element() - /// 7.2.20 parseTypeOtherPropertyElt - /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) - /// propertyEltList - /// end-element() - /// 7.2.21 emptyPropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) - /// end-element() - /// The various property element forms are not distinguished by the XML element name, - /// but by their attributes for the most part. - /// - /// - /// 7.2.14 propertyElt - /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | - /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | - /// parseTypeOtherPropertyElt | emptyPropertyElt - /// 7.2.15 resourcePropertyElt - /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) - /// ws* nodeElement ws - /// end-element() - /// 7.2.16 literalPropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) - /// text() - /// end-element() - /// 7.2.17 parseTypeLiteralPropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) - /// literal - /// end-element() - /// 7.2.18 parseTypeResourcePropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) - /// propertyEltList - /// end-element() - /// 7.2.19 parseTypeCollectionPropertyElt - /// start-element ( - /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) - /// nodeElementList - /// end-element() - /// 7.2.20 parseTypeOtherPropertyElt - /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) - /// propertyEltList - /// end-element() - /// 7.2.21 emptyPropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) - /// end-element() - /// The various property element forms are not distinguished by the XML element name, - /// but by their attributes for the most part. The exceptions are resourcePropertyElt and - /// literalPropertyElt. They are distinguished by their XML element content. - /// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can - /// appear in many of these. We have to allow for it in the attibute counts below. - /// - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_PropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) - { - int nodeTerm = GetRDFTermKind(xmlNode); - if (!IsPropertyElementName(nodeTerm)) - { - throw new XMPException("Invalid property element name", XMPErrorConstants.Badrdf); - } - // remove the namespace-definitions from the list - XmlAttributeCollection attributes = xmlNode.Attributes; - IList nsAttrs = null; - for (int i = 0; i < attributes.Count; i++) - { - XmlNode attribute = attributes.Item(i); - if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) - { - if (nsAttrs == null) - { - nsAttrs = new ArrayList(); - } - nsAttrs.Add(attribute.Name); - } - } - if (nsAttrs != null) - { - for (Iterator it = nsAttrs.Iterator(); it.HasNext(); ) - { - string ns = (string)it.Next(); - attributes.RemoveNamedItem(ns); - } - } - if (attributes.Count > 3) - { - // Only an emptyPropertyElt can have more than 3 attributes. - Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); - } - else - { - // Look through the attributes for one that isn't rdf:ID or xml:lang, - // it will usually tell what we should be dealing with. - // The called routines must verify their specific syntax! - for (int i_1 = 0; i_1 < attributes.Count; i_1++) - { - XmlNode attribute = attributes.Item(i_1); - string attrLocal = attribute.LocalName; - string attrNS = attribute.NamespaceURI; - string attrValue = attribute.Value; - if (!(XMPConstConstants.XmlLang.Equals(attribute.Name) && !("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)))) - { - if ("datatype".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)) - { - Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); - } - else - { - if (!("parseType".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS))) - { - Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); - } - else - { - if ("Literal".Equals(attrValue)) - { - Rdf_ParseTypeLiteralPropertyElement(); - } - else - { - if ("Resource".Equals(attrValue)) - { - Rdf_ParseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel); - } - else - { - if ("Collection".Equals(attrValue)) - { - Rdf_ParseTypeCollectionPropertyElement(); - } - else - { - Rdf_ParseTypeOtherPropertyElement(); - } - } - } - } - } - return; - } - } - // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, - // or an emptyPropertyElt. Look at the child XML nodes to decide which. - if (xmlNode.HasChildNodes) - { - for (int i_2 = 0; i_2 < xmlNode.ChildNodes.Count; i_2++) - { - XmlNode currChild = xmlNode.ChildNodes.Item(i_2); - if (currChild.NodeType != XmlNodeType.Text) - { - Rdf_ResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel); - return; - } - } - Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); - } - else - { - Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); - } - } - } - - /// - /// 7.2.15 resourcePropertyElt - /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) - /// ws* nodeElement ws - /// end-element() - /// This handles structs using an rdf:Description node, - /// arrays using rdf:Bag/Seq/Alt, and typedNodes. - /// - /// - /// 7.2.15 resourcePropertyElt - /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) - /// ws* nodeElement ws - /// end-element() - /// This handles structs using an rdf:Description node, - /// arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified - /// properties written with rdf:Description and rdf:value. - /// - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_ResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) - { - if (isTopLevel && "iX:changes".Equals(xmlNode.Name)) - { - // Strip old "punchcard" chaff which has on the prefix "iX:". - return; - } - XMPNode newCompound = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); - // walk through the attributes - for (int i = 0; i < xmlNode.Attributes.Count; i++) - { - XmlNode attribute = xmlNode.Attributes.Item(i); - if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) - { - continue; - } - string attrLocal = attribute.LocalName; - string attrNS = attribute.NamespaceURI; - if (XMPConstConstants.XmlLang.Equals(attribute.Name)) - { - AddQualifierNode(newCompound, XMPConstConstants.XmlLang, attribute.Value); - } - else - { - if ("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)) - { - continue; - } - else - { - // Ignore all rdf:ID attributes. - throw new XMPException("Invalid attribute for resource property element", XMPErrorConstants.Badrdf); - } - } - } - // walk through the children - XmlNode currChild = null; - bool found = false; - int i_1; - for (i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++) - { - currChild = xmlNode.ChildNodes.Item(i_1); - if (!IsWhitespaceNode(currChild)) - { - if (currChild.NodeType == XmlNodeType.Element && !found) - { - bool isRDF = XMPConstConstants.NsRdf.Equals(currChild.NamespaceURI); - string childLocal = currChild.LocalName; - if (isRDF && "Bag".Equals(childLocal)) - { - newCompound.GetOptions().SetArray(true); - } - else - { - if (isRDF && "Seq".Equals(childLocal)) - { - newCompound.GetOptions().SetArray(true).SetArrayOrdered(true); - } - else - { - if (isRDF && "Alt".Equals(childLocal)) - { - newCompound.GetOptions().SetArray(true).SetArrayOrdered(true).SetArrayAlternate(true); - } - else - { - newCompound.GetOptions().SetStruct(true); - if (!isRDF && !"Description".Equals(childLocal)) - { - string typeName = currChild.NamespaceURI; - if (typeName == null) - { - throw new XMPException("All XML elements must be in a namespace", XMPErrorConstants.Badxmp); - } - typeName += ':' + childLocal; - AddQualifierNode(newCompound, "rdf:type", typeName); - } - } - } - } - Rdf_NodeElement(xmp, newCompound, currChild, false); - if (newCompound.GetHasValueChild()) - { - FixupQualifiedNode(newCompound); - } - else - { - if (newCompound.GetOptions().IsArrayAlternate()) - { - XMPNodeUtils.DetectAltText(newCompound); - } - } - found = true; - } - else - { - if (found) - { - // found second child element - throw new XMPException("Invalid child of resource property element", XMPErrorConstants.Badrdf); - } - else - { - throw new XMPException("Children of resource property element must be XML elements", XMPErrorConstants.Badrdf); - } - } - } - } - if (!found) - { - // didn't found any child elements - throw new XMPException("Missing child of resource property element", XMPErrorConstants.Badrdf); - } - } - - /// - /// 7.2.16 literalPropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( idAttr?, datatypeAttr?) ) - /// text() - /// end-element() - /// Add a leaf node with the text value and qualifiers for the attributes. - /// - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_LiteralPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) - { - XMPNode newChild = AddChildNode(xmp, xmpParent, xmlNode, null, isTopLevel); - for (int i = 0; i < xmlNode.Attributes.Count; i++) - { - XmlNode attribute = xmlNode.Attributes.Item(i); - if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) - { - continue; - } - string attrNS = attribute.NamespaceURI; - string attrLocal = attribute.LocalName; - if (XMPConstConstants.XmlLang.Equals(attribute.Name)) - { - AddQualifierNode(newChild, XMPConstConstants.XmlLang, attribute.Value); - } - else - { - if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "datatype".Equals(attrLocal))) - { - continue; - } - else - { - // Ignore all rdf:ID and rdf:datatype attributes. - throw new XMPException("Invalid attribute for literal property element", XMPErrorConstants.Badrdf); - } - } - } - string textValue = string.Empty; - for (int i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++) - { - XmlNode child = xmlNode.ChildNodes.Item(i_1); - if (child.NodeType == XmlNodeType.Text) - { - textValue += child.Value; - } - else - { - throw new XMPException("Invalid child of literal property element", XMPErrorConstants.Badrdf); - } - } - newChild.SetValue(textValue); - } - - /// - /// 7.2.17 parseTypeLiteralPropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( idAttr?, parseLiteral ) ) - /// literal - /// end-element() - /// - /// thown on parsing errors - private static void Rdf_ParseTypeLiteralPropertyElement() - { - throw new XMPException("ParseTypeLiteral property element not allowed", XMPErrorConstants.Badxmp); - } - - /// - /// 7.2.18 parseTypeResourcePropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( idAttr?, parseResource ) ) - /// propertyEltList - /// end-element() - /// Add a new struct node with a qualifier for the possible rdf:ID attribute. - /// - /// - /// 7.2.18 parseTypeResourcePropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( idAttr?, parseResource ) ) - /// propertyEltList - /// end-element() - /// Add a new struct node with a qualifier for the possible rdf:ID attribute. - /// Then process the XML child nodes to get the struct fields. - /// - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_ParseTypeResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) - { - XMPNode newStruct = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); - newStruct.GetOptions().SetStruct(true); - for (int i = 0; i < xmlNode.Attributes.Count; i++) - { - XmlNode attribute = xmlNode.Attributes.Item(i); - if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) - { - continue; - } - string attrLocal = attribute.LocalName; - string attrNS = attribute.NamespaceURI; - if (XMPConstConstants.XmlLang.Equals(attribute.Name)) - { - AddQualifierNode(newStruct, XMPConstConstants.XmlLang, attribute.Value); - } - else - { - if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "parseType".Equals(attrLocal))) - { - continue; - } - else - { - // The caller ensured the value is "Resource". - // Ignore all rdf:ID attributes. - throw new XMPException("Invalid attribute for ParseTypeResource property element", XMPErrorConstants.Badrdf); - } - } - } - Rdf_PropertyElementList(xmp, newStruct, xmlNode, false); - if (newStruct.GetHasValueChild()) - { - FixupQualifiedNode(newStruct); - } - } - - /// - /// 7.2.19 parseTypeCollectionPropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( idAttr?, parseCollection ) ) - /// nodeElementList - /// end-element() - /// - /// thown on parsing errors - private static void Rdf_ParseTypeCollectionPropertyElement() - { - throw new XMPException("ParseTypeCollection property element not allowed", XMPErrorConstants.Badxmp); - } - - /// - /// 7.2.20 parseTypeOtherPropertyElt - /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) - /// propertyEltList - /// end-element() - /// - /// thown on parsing errors - private static void Rdf_ParseTypeOtherPropertyElement() - { - throw new XMPException("ParseTypeOther property element not allowed", XMPErrorConstants.Badxmp); - } - - /// - /// 7.2.21 emptyPropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( - /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) - /// end-element() - /// - /// - /// - /// - /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of - /// attributes. - /// - /// - /// 7.2.21 emptyPropertyElt - /// start-element ( URI == propertyElementURIs, - /// attributes == set ( - /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) - /// end-element() - /// - /// - /// - /// - /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of - /// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a - /// simple property with an empty value (ns:Prop1), a simple property whose value is a URI - /// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). - /// An emptyPropertyElt can also represent an XMP struct whose fields are all simple and - /// unqualified (ns:Prop4). - /// It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the - /// verbose form written using a literalPropertyElt. - /// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for - /// design reasons and partly for historical reasons. The XMP mapping rules are: - ///
    - ///
  1. If there is an rdf:value attribute then this is a simple property - /// with a text value. - /// All other attributes are qualifiers. - ///
  2. If there is an rdf:resource attribute then this is a simple property - /// with a URI value. - /// All other attributes are qualifiers. - ///
  3. If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID - /// then this is a simple - /// property with an empty value. - ///
  4. Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, - /// or rdf:nodeID are fields. - ///
- ///
- /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Flag if the node is a top-level node - /// thown on parsing errors - private static void Rdf_EmptyPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) - { - bool hasPropertyAttrs = false; - bool hasResourceAttr = false; - bool hasNodeIDAttr = false; - bool hasValueAttr = false; - XmlNode valueNode = null; - // ! Can come from rdf:value or rdf:resource. - if (xmlNode.HasChildNodes) - { - throw new XMPException("Nested content not allowed with rdf:resource or property attributes", XMPErrorConstants.Badrdf); - } - // First figure out what XMP this maps to and remember the XML node for a simple value. - for (int i = 0; i < xmlNode.Attributes.Count; i++) - { - XmlNode attribute = xmlNode.Attributes.Item(i); - if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) - { - continue; - } - int attrTerm = GetRDFTermKind(attribute); - switch (attrTerm) - { - case RdftermId: - { - // Nothing to do. - break; - } - - case RdftermResource: - { - if (hasNodeIDAttr) - { - throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf); - } - else - { - if (hasValueAttr) - { - throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp); - } - } - hasResourceAttr = true; - if (!hasValueAttr) - { - valueNode = attribute; - } - break; - } - - case RdftermNodeId: - { - if (hasResourceAttr) - { - throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf); - } - hasNodeIDAttr = true; - break; - } - - case RdftermOther: - { - if ("value".Equals(attribute.LocalName) && XMPConstConstants.NsRdf.Equals(attribute.NamespaceURI)) - { - if (hasResourceAttr) - { - throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp); - } - hasValueAttr = true; - valueNode = attribute; - } - else - { - if (!XMPConstConstants.XmlLang.Equals(attribute.Name)) - { - hasPropertyAttrs = true; - } - } - break; - } - - default: - { - throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.Badrdf); - } - } - } - // Create the right kind of child node and visit the attributes again - // to add the fields or qualifiers. - // ! Because of implementation vagaries, - // the xmpParent is the tree root for top level properties. - // ! The schema is found, created if necessary, by addChildNode. - XMPNode childNode = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); - bool childIsStruct = false; - if (hasValueAttr || hasResourceAttr) - { - childNode.SetValue(valueNode != null ? valueNode.Value : string.Empty); - if (!hasValueAttr) - { - // ! Might have both rdf:value and rdf:resource. - childNode.GetOptions().SetURI(true); - } - } - else - { - if (hasPropertyAttrs) - { - childNode.GetOptions().SetStruct(true); - childIsStruct = true; - } - } - for (int i_1 = 0; i_1 < xmlNode.Attributes.Count; i_1++) - { - XmlNode attribute = xmlNode.Attributes.Item(i_1); - if (attribute == valueNode || "xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) - { - continue; - } - // Skip the rdf:value or rdf:resource attribute holding the value. - int attrTerm = GetRDFTermKind(attribute); - switch (attrTerm) - { - case RdftermId: - case RdftermNodeId: - { - break; - } - - case RdftermResource: - { - // Ignore all rdf:ID and rdf:nodeID attributes. - AddQualifierNode(childNode, "rdf:resource", attribute.Value); - break; - } - - case RdftermOther: - { - if (!childIsStruct) - { - AddQualifierNode(childNode, attribute.Name, attribute.Value); - } - else - { - if (XMPConstConstants.XmlLang.Equals(attribute.Name)) - { - AddQualifierNode(childNode, XMPConstConstants.XmlLang, attribute.Value); - } - else - { - AddChildNode(xmp, childNode, attribute, attribute.Value, false); - } - } - break; - } - - default: - { - throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.Badrdf); - } - } - } - } - - /// Adds a child node. - /// the xmp metadata object that is generated - /// the parent xmp node - /// the currently processed XML node - /// Node value - /// Flag if the node is a top-level node - /// Returns the newly created child node. - /// thown on parsing errors - private static XMPNode AddChildNode(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, string value, bool isTopLevel) - { - XMPSchemaRegistry registry = XMPMetaFactory.GetSchemaRegistry(); - string @namespace = xmlNode.NamespaceURI; - string childName; - if (@namespace != null) - { - if (XMPConstConstants.NsDcDeprecated.Equals(@namespace)) - { - // Fix a legacy DC namespace - @namespace = XMPConstConstants.NsDc; - } - string prefix = registry.GetNamespacePrefix(@namespace); - if (prefix == null) - { - prefix = xmlNode.Prefix != null ? xmlNode.Prefix : DefaultPrefix; - prefix = registry.RegisterNamespace(@namespace, prefix); - } - childName = prefix + xmlNode.LocalName; - } - else - { - throw new XMPException("XML namespace required for all elements and attributes", XMPErrorConstants.Badrdf); - } - // create schema node if not already there - PropertyOptions childOptions = new PropertyOptions(); - bool isAlias = false; - if (isTopLevel) - { - // Lookup the schema node, adjust the XMP parent pointer. - // Incoming parent must be the tree root. - XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), @namespace, DefaultPrefix, true); - schemaNode.SetImplicit(false); - // Clear the implicit node bit. - // need runtime check for proper 32 bit code. - xmpParent = schemaNode; - // If this is an alias set the alias flag in the node - // and the hasAliases flag in the tree. - if (registry.FindAlias(childName) != null) - { - isAlias = true; - xmp.GetRoot().SetHasAliases(true); - schemaNode.SetHasAliases(true); - } - } - // Make sure that this is not a duplicate of a named node. - bool isArrayItem = "rdf:li".Equals(childName); - bool isValueNode = "rdf:value".Equals(childName); - // Create XMP node and so some checks - XMPNode newChild = new XMPNode(childName, value, childOptions); - newChild.SetAlias(isAlias); - // Add the new child to the XMP parent node, a value node first. - if (!isValueNode) - { - xmpParent.AddChild(newChild); - } - else - { - xmpParent.AddChild(1, newChild); - } - if (isValueNode) - { - if (isTopLevel || !xmpParent.GetOptions().IsStruct()) - { - throw new XMPException("Misplaced rdf:value element", XMPErrorConstants.Badrdf); - } - xmpParent.SetHasValueChild(true); - } - if (isArrayItem) - { - if (!xmpParent.GetOptions().IsArray()) - { - throw new XMPException("Misplaced rdf:li element", XMPErrorConstants.Badrdf); - } - newChild.SetName(XMPConstConstants.ArrayItemName); - } - return newChild; - } - - /// Adds a qualifier node. - /// the parent xmp node - /// - /// the name of the qualifier which has to be - /// QName including the default prefix - /// - /// the value of the qualifier - /// Returns the newly created child node. - /// thown on parsing errors - private static XMPNode AddQualifierNode(XMPNode xmpParent, string name, string value) - { - bool isLang = XMPConstConstants.XmlLang.Equals(name); - XMPNode newQual = null; - // normalize value of language qualifiers - newQual = new XMPNode(name, isLang ? Utils.NormalizeLangValue(value) : value, null); - xmpParent.AddQualifier(newQual); - return newQual; - } - - /// The parent is an RDF pseudo-struct containing an rdf:value field. - /// - /// The parent is an RDF pseudo-struct containing an rdf:value field. Fix the - /// XMP data model. The rdf:value node must be the first child, the other - /// children are qualifiers. The form, value, and children of the rdf:value - /// node are the real ones. The rdf:value node's qualifiers must be added to - /// the others. - /// - /// the parent xmp node - /// thown on parsing errors - private static void FixupQualifiedNode(XMPNode xmpParent) - { - Debug.Assert(xmpParent.GetOptions().IsStruct() && xmpParent.HasChildren()); - XMPNode valueNode = xmpParent.GetChild(1); - Debug.Assert("rdf:value".Equals(valueNode.GetName())); - // Move the qualifiers on the value node to the parent. - // Make sure an xml:lang qualifier stays at the front. - // Check for duplicate names between the value node's qualifiers and the parent's children. - // The parent's children are about to become qualifiers. Check here, between the groups. - // Intra-group duplicates are caught by XMPNode#addChild(...). - if (valueNode.GetOptions().GetHasLanguage()) - { - if (xmpParent.GetOptions().GetHasLanguage()) - { - throw new XMPException("Redundant xml:lang for rdf:value element", XMPErrorConstants.Badxmp); - } - XMPNode langQual = valueNode.GetQualifier(1); - valueNode.RemoveQualifier(langQual); - xmpParent.AddQualifier(langQual); - } - // Start the remaining copy after the xml:lang qualifier. - for (int i = 1; i <= valueNode.GetQualifierLength(); i++) - { - XMPNode qualifier = valueNode.GetQualifier(i); - xmpParent.AddQualifier(qualifier); - } - // Change the parent's other children into qualifiers. - // This loop starts at 1, child 0 is the rdf:value node. - for (int i_1 = 2; i_1 <= xmpParent.GetChildrenLength(); i_1++) - { - XMPNode qualifier = xmpParent.GetChild(i_1); - xmpParent.AddQualifier(qualifier); - } - // Move the options and value last, other checks need the parent's original options. - // Move the value node's children to be the parent's children. - Debug.Assert(xmpParent.GetOptions().IsStruct() || xmpParent.GetHasValueChild()); - xmpParent.SetHasValueChild(false); - xmpParent.GetOptions().SetStruct(false); - xmpParent.GetOptions().MergeWith(valueNode.GetOptions()); - xmpParent.SetValue(valueNode.GetValue()); - xmpParent.RemoveChildren(); - for (Iterator it = valueNode.IterateChildren(); it.HasNext(); ) - { - XMPNode child = (XMPNode)it.Next(); - xmpParent.AddChild(child); - } - } - - /// Checks if the node is a white space. - /// an XML-node - /// - /// Returns whether the node is a whitespace node, - /// i.e. a text node that contains only whitespaces. - /// - private static bool IsWhitespaceNode(XmlNode node) - { - if (node.NodeType != XmlNodeType.Text) - { - return false; - } - string value = node.Value; - for (int i = 0; i < value.Length; i++) - { - if (!char.IsWhiteSpace(value[i])) - { - return false; - } - } - return true; - } - - /// - /// 7.2.6 propertyElementURIs - /// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms ) - /// - /// the term id - /// Return true if the term is a property element name. - private static bool IsPropertyElementName(int term) - { - if (term == RdftermDescription || IsOldTerm(term)) - { - return false; - } - else - { - return (!IsCoreSyntaxTerm(term)); - } - } - - /// - /// 7.2.4 oldTerms
- /// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID - ///
- /// the term id - /// Returns true if the term is an old term. - private static bool IsOldTerm(int term) - { - return RdftermFirstOld <= term && term <= RdftermLastOld; - } - - /// - /// 7.2.2 coreSyntaxTerms
- /// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | - /// rdf:datatype - ///
- /// the term id - /// Return true if the term is a core syntax term - private static bool IsCoreSyntaxTerm(int term) - { - return RdftermFirstCore <= term && term <= RdftermLastCore; - } - - /// Determines the ID for a certain RDF Term. - /// - /// Determines the ID for a certain RDF Term. - /// Arranged to hopefully minimize the parse time for large XMP. - /// - /// an XML node - /// Returns the term ID. - private static int GetRDFTermKind(XmlNode node) - { - string localName = node.LocalName; - string @namespace = node.NamespaceURI; - if (@namespace == null && ("about".Equals(localName) || "ID".Equals(localName)) && (node is XmlAttribute) && XMPConstConstants.NsRdf.Equals(((XmlAttribute)node).OwnerElement.NamespaceURI)) - { - @namespace = XMPConstConstants.NsRdf; - } - if (XMPConstConstants.NsRdf.Equals(@namespace)) - { - if ("li".Equals(localName)) - { - return RdftermLi; - } - else - { - if ("parseType".Equals(localName)) - { - return RdftermParseType; - } - else - { - if ("Description".Equals(localName)) - { - return RdftermDescription; - } - else - { - if ("about".Equals(localName)) - { - return RdftermAbout; - } - else - { - if ("resource".Equals(localName)) - { - return RdftermResource; - } - else - { - if ("RDF".Equals(localName)) - { - return RdftermRdf; - } - else - { - if ("ID".Equals(localName)) - { - return RdftermId; - } - else - { - if ("nodeID".Equals(localName)) - { - return RdftermNodeId; - } - else - { - if ("datatype".Equals(localName)) - { - return RdftermDatatype; - } - else - { - if ("aboutEach".Equals(localName)) - { - return RdftermAboutEach; - } - else - { - if ("aboutEachPrefix".Equals(localName)) - { - return RdftermAboutEachPrefix; - } - else - { - if ("bagID".Equals(localName)) - { - return RdftermBagId; - } - } - } - } - } - } - } - } - } - } - } - } - } - return RdftermOther; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Collections; +using System.Diagnostics; +using System.Xml; +using Com.Adobe.Xmp.Options; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// Parser for "normal" XML serialisation of RDF. + /// 14.07.2006 + public class ParseRDF : XMPError, XMPConst + { + public const int RdftermOther = 0; + + /// Start of coreSyntaxTerms. + public const int RdftermRdf = 1; + + public const int RdftermId = 2; + + public const int RdftermAbout = 3; + + public const int RdftermParseType = 4; + + public const int RdftermResource = 5; + + public const int RdftermNodeId = 6; + + /// End of coreSyntaxTerms + public const int RdftermDatatype = 7; + + /// Start of additions for syntax Terms. + public const int RdftermDescription = 8; + + /// End of of additions for syntaxTerms. + public const int RdftermLi = 9; + + /// Start of oldTerms. + public const int RdftermAboutEach = 10; + + public const int RdftermAboutEachPrefix = 11; + + /// End of oldTerms. + public const int RdftermBagId = 12; + + public const int RdftermFirstCore = RdftermRdf; + + public const int RdftermLastCore = RdftermDatatype; + + /// ! Yes, the syntax terms include the core terms. + public const int RdftermFirstSyntax = RdftermFirstCore; + + public const int RdftermLastSyntax = RdftermLi; + + public const int RdftermFirstOld = RdftermAboutEach; + + public const int RdftermLastOld = RdftermBagId; + + /// this prefix is used for default namespaces + public const string DefaultPrefix = "_dflt"; + + /// The main parsing method. + /// + /// The main parsing method. The XML tree is walked through from the root node and and XMP tree + /// is created. This is a raw parse, the normalisation of the XMP tree happens outside. + /// + /// the XML root node + /// Returns an XMP metadata object (not normalized) + /// Occurs if the parsing fails for any reason. + internal static XMPMetaImpl Parse(XmlNode xmlRoot) + { + XMPMetaImpl xmp = new XMPMetaImpl(); + Rdf_RDF(xmp, xmlRoot); + return xmp; + } + + /// + /// Each of these parsing methods is responsible for recognizing an RDF + /// syntax production and adding the appropriate structure to the XMP tree. + /// + /// + /// Each of these parsing methods is responsible for recognizing an RDF + /// syntax production and adding the appropriate structure to the XMP tree. + /// They simply return for success, failures will throw an exception. + /// + /// the xmp metadata object that is generated + /// the top-level xml node + /// thown on parsing errors + internal static void Rdf_RDF(XMPMetaImpl xmp, XmlNode rdfRdfNode) + { + if (rdfRdfNode.HasAttributes()) + { + Rdf_NodeElementList(xmp, xmp.GetRoot(), rdfRdfNode); + } + else + { + throw new XMPException("Invalid attributes of rdf:RDF element", XMPErrorConstants.Badrdf); + } + } + + /// + /// 7.2.10 nodeElementList
+ /// ws* ( nodeElement ws* ) + /// Note: this method is only called from the rdf:RDF-node (top level) + ///
+ /// the xmp metadata object that is generated + /// the parent xmp node + /// the top-level xml node + /// thown on parsing errors + private static void Rdf_NodeElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode rdfRdfNode) + { + for (int i = 0; i < rdfRdfNode.ChildNodes.Count; i++) + { + XmlNode child = rdfRdfNode.ChildNodes.Item(i); + // filter whitespaces (and all text nodes) + if (!IsWhitespaceNode(child)) + { + Rdf_NodeElement(xmp, xmpParent, child, true); + } + } + } + + /// + /// 7.2.5 nodeElementURIs + /// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) + /// 7.2.11 nodeElement + /// start-element ( URI == nodeElementURIs, + /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) + /// propertyEltList + /// end-element() + /// A node element URI is rdf:Description or anything else that is not an RDF + /// term. + /// + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_NodeElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) + { + int nodeTerm = GetRDFTermKind(xmlNode); + if (nodeTerm != RdftermDescription && nodeTerm != RdftermOther) + { + throw new XMPException("Node element must be rdf:Description or typed node", XMPErrorConstants.Badrdf); + } + else + { + if (isTopLevel && nodeTerm == RdftermOther) + { + throw new XMPException("Top level typed node not allowed", XMPErrorConstants.Badxmp); + } + else + { + Rdf_NodeElementAttrs(xmp, xmpParent, xmlNode, isTopLevel); + Rdf_PropertyElementList(xmp, xmpParent, xmlNode, isTopLevel); + } + } + } + + /// + /// 7.2.7 propertyAttributeURIs + /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) + /// 7.2.11 nodeElement + /// start-element ( URI == nodeElementURIs, + /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) + /// propertyEltList + /// end-element() + /// Process the attribute list for an RDF node element. + /// + /// + /// 7.2.7 propertyAttributeURIs + /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) + /// 7.2.11 nodeElement + /// start-element ( URI == nodeElementURIs, + /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) + /// propertyEltList + /// end-element() + /// Process the attribute list for an RDF node element. A property attribute URI is + /// anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, + /// as are rdf:about attributes on inner nodes. + /// + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) + { + // Used to detect attributes that are mutually exclusive. + int exclusiveAttrs = 0; + for (int i = 0; i < xmlNode.Attributes.Count; i++) + { + XmlNode attribute = xmlNode.Attributes.Item(i); + // quick hack, ns declarations do not appear in C++ + // ignore "ID" without namespace + if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) + { + continue; + } + int attrTerm = GetRDFTermKind(attribute); + switch (attrTerm) + { + case RdftermId: + case RdftermNodeId: + case RdftermAbout: + { + if (exclusiveAttrs > 0) + { + throw new XMPException("Mutally exclusive about, ID, nodeID attributes", XMPErrorConstants.Badrdf); + } + exclusiveAttrs++; + if (isTopLevel && (attrTerm == RdftermAbout)) + { + // This is the rdf:about attribute on a top level node. Set + // the XMP tree name if + // it doesn't have a name yet. Make sure this name matches + // the XMP tree name. + if (xmpParent.GetName() != null && xmpParent.GetName().Length > 0) + { + if (!xmpParent.GetName().Equals(attribute.Value)) + { + throw new XMPException("Mismatched top level rdf:about values", XMPErrorConstants.Badxmp); + } + } + else + { + xmpParent.SetName(attribute.Value); + } + } + break; + } + + case RdftermOther: + { + AddChildNode(xmp, xmpParent, attribute, attribute.Value, isTopLevel); + break; + } + + default: + { + throw new XMPException("Invalid nodeElement attribute", XMPErrorConstants.Badrdf); + } + } + } + } + + /// + /// 7.2.13 propertyEltList + /// ws* ( propertyElt ws* ) + /// + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_PropertyElementList(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlParent, bool isTopLevel) + { + for (int i = 0; i < xmlParent.ChildNodes.Count; i++) + { + XmlNode currChild = xmlParent.ChildNodes.Item(i); + if (IsWhitespaceNode(currChild)) + { + continue; + } + else + { + if (currChild.NodeType != XmlNodeType.Element) + { + throw new XMPException("Expected property element node not found", XMPErrorConstants.Badrdf); + } + else + { + Rdf_PropertyElement(xmp, xmpParent, currChild, isTopLevel); + } + } + } + } + + /// + /// 7.2.14 propertyElt + /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | + /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | + /// parseTypeOtherPropertyElt | emptyPropertyElt + /// 7.2.15 resourcePropertyElt + /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + /// ws* nodeElement ws + /// end-element() + /// 7.2.16 literalPropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) + /// text() + /// end-element() + /// 7.2.17 parseTypeLiteralPropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) + /// literal + /// end-element() + /// 7.2.18 parseTypeResourcePropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) + /// propertyEltList + /// end-element() + /// 7.2.19 parseTypeCollectionPropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) + /// nodeElementList + /// end-element() + /// 7.2.20 parseTypeOtherPropertyElt + /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) + /// propertyEltList + /// end-element() + /// 7.2.21 emptyPropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) + /// end-element() + /// The various property element forms are not distinguished by the XML element name, + /// but by their attributes for the most part. + /// + /// + /// 7.2.14 propertyElt + /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | + /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | + /// parseTypeOtherPropertyElt | emptyPropertyElt + /// 7.2.15 resourcePropertyElt + /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + /// ws* nodeElement ws + /// end-element() + /// 7.2.16 literalPropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) + /// text() + /// end-element() + /// 7.2.17 parseTypeLiteralPropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) + /// literal + /// end-element() + /// 7.2.18 parseTypeResourcePropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) + /// propertyEltList + /// end-element() + /// 7.2.19 parseTypeCollectionPropertyElt + /// start-element ( + /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) + /// nodeElementList + /// end-element() + /// 7.2.20 parseTypeOtherPropertyElt + /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) + /// propertyEltList + /// end-element() + /// 7.2.21 emptyPropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) + /// end-element() + /// The various property element forms are not distinguished by the XML element name, + /// but by their attributes for the most part. The exceptions are resourcePropertyElt and + /// literalPropertyElt. They are distinguished by their XML element content. + /// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can + /// appear in many of these. We have to allow for it in the attibute counts below. + /// + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_PropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) + { + int nodeTerm = GetRDFTermKind(xmlNode); + if (!IsPropertyElementName(nodeTerm)) + { + throw new XMPException("Invalid property element name", XMPErrorConstants.Badrdf); + } + // remove the namespace-definitions from the list + XmlAttributeCollection attributes = xmlNode.Attributes; + IList nsAttrs = null; + for (int i = 0; i < attributes.Count; i++) + { + XmlNode attribute = attributes.Item(i); + if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) + { + if (nsAttrs == null) + { + nsAttrs = new ArrayList(); + } + nsAttrs.Add(attribute.Name); + } + } + if (nsAttrs != null) + { + for (Iterator it = nsAttrs.Iterator(); it.HasNext(); ) + { + string ns = (string)it.Next(); + attributes.RemoveNamedItem(ns); + } + } + if (attributes.Count > 3) + { + // Only an emptyPropertyElt can have more than 3 attributes. + Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); + } + else + { + // Look through the attributes for one that isn't rdf:ID or xml:lang, + // it will usually tell what we should be dealing with. + // The called routines must verify their specific syntax! + for (int i_1 = 0; i_1 < attributes.Count; i_1++) + { + XmlNode attribute = attributes.Item(i_1); + string attrLocal = attribute.LocalName; + string attrNS = attribute.NamespaceURI; + string attrValue = attribute.Value; + if (!(XMPConstConstants.XmlLang.Equals(attribute.Name) && !("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)))) + { + if ("datatype".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)) + { + Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); + } + else + { + if (!("parseType".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS))) + { + Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); + } + else + { + if ("Literal".Equals(attrValue)) + { + Rdf_ParseTypeLiteralPropertyElement(); + } + else + { + if ("Resource".Equals(attrValue)) + { + Rdf_ParseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel); + } + else + { + if ("Collection".Equals(attrValue)) + { + Rdf_ParseTypeCollectionPropertyElement(); + } + else + { + Rdf_ParseTypeOtherPropertyElement(); + } + } + } + } + } + return; + } + } + // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, + // or an emptyPropertyElt. Look at the child XML nodes to decide which. + if (xmlNode.HasChildNodes) + { + for (int i_2 = 0; i_2 < xmlNode.ChildNodes.Count; i_2++) + { + XmlNode currChild = xmlNode.ChildNodes.Item(i_2); + if (currChild.NodeType != XmlNodeType.Text) + { + Rdf_ResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel); + return; + } + } + Rdf_LiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); + } + else + { + Rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); + } + } + } + + /// + /// 7.2.15 resourcePropertyElt + /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + /// ws* nodeElement ws + /// end-element() + /// This handles structs using an rdf:Description node, + /// arrays using rdf:Bag/Seq/Alt, and typedNodes. + /// + /// + /// 7.2.15 resourcePropertyElt + /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + /// ws* nodeElement ws + /// end-element() + /// This handles structs using an rdf:Description node, + /// arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified + /// properties written with rdf:Description and rdf:value. + /// + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_ResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) + { + if (isTopLevel && "iX:changes".Equals(xmlNode.Name)) + { + // Strip old "punchcard" chaff which has on the prefix "iX:". + return; + } + XMPNode newCompound = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); + // walk through the attributes + for (int i = 0; i < xmlNode.Attributes.Count; i++) + { + XmlNode attribute = xmlNode.Attributes.Item(i); + if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) + { + continue; + } + string attrLocal = attribute.LocalName; + string attrNS = attribute.NamespaceURI; + if (XMPConstConstants.XmlLang.Equals(attribute.Name)) + { + AddQualifierNode(newCompound, XMPConstConstants.XmlLang, attribute.Value); + } + else + { + if ("ID".Equals(attrLocal) && XMPConstConstants.NsRdf.Equals(attrNS)) + { + continue; + } + else + { + // Ignore all rdf:ID attributes. + throw new XMPException("Invalid attribute for resource property element", XMPErrorConstants.Badrdf); + } + } + } + // walk through the children + XmlNode currChild = null; + bool found = false; + int i_1; + for (i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++) + { + currChild = xmlNode.ChildNodes.Item(i_1); + if (!IsWhitespaceNode(currChild)) + { + if (currChild.NodeType == XmlNodeType.Element && !found) + { + bool isRDF = XMPConstConstants.NsRdf.Equals(currChild.NamespaceURI); + string childLocal = currChild.LocalName; + if (isRDF && "Bag".Equals(childLocal)) + { + newCompound.GetOptions().SetArray(true); + } + else + { + if (isRDF && "Seq".Equals(childLocal)) + { + newCompound.GetOptions().SetArray(true).SetArrayOrdered(true); + } + else + { + if (isRDF && "Alt".Equals(childLocal)) + { + newCompound.GetOptions().SetArray(true).SetArrayOrdered(true).SetArrayAlternate(true); + } + else + { + newCompound.GetOptions().SetStruct(true); + if (!isRDF && !"Description".Equals(childLocal)) + { + string typeName = currChild.NamespaceURI; + if (typeName == null) + { + throw new XMPException("All XML elements must be in a namespace", XMPErrorConstants.Badxmp); + } + typeName += ':' + childLocal; + AddQualifierNode(newCompound, "rdf:type", typeName); + } + } + } + } + Rdf_NodeElement(xmp, newCompound, currChild, false); + if (newCompound.GetHasValueChild()) + { + FixupQualifiedNode(newCompound); + } + else + { + if (newCompound.GetOptions().IsArrayAlternate()) + { + XMPNodeUtils.DetectAltText(newCompound); + } + } + found = true; + } + else + { + if (found) + { + // found second child element + throw new XMPException("Invalid child of resource property element", XMPErrorConstants.Badrdf); + } + else + { + throw new XMPException("Children of resource property element must be XML elements", XMPErrorConstants.Badrdf); + } + } + } + } + if (!found) + { + // didn't found any child elements + throw new XMPException("Missing child of resource property element", XMPErrorConstants.Badrdf); + } + } + + /// + /// 7.2.16 literalPropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( idAttr?, datatypeAttr?) ) + /// text() + /// end-element() + /// Add a leaf node with the text value and qualifiers for the attributes. + /// + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_LiteralPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) + { + XMPNode newChild = AddChildNode(xmp, xmpParent, xmlNode, null, isTopLevel); + for (int i = 0; i < xmlNode.Attributes.Count; i++) + { + XmlNode attribute = xmlNode.Attributes.Item(i); + if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) + { + continue; + } + string attrNS = attribute.NamespaceURI; + string attrLocal = attribute.LocalName; + if (XMPConstConstants.XmlLang.Equals(attribute.Name)) + { + AddQualifierNode(newChild, XMPConstConstants.XmlLang, attribute.Value); + } + else + { + if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "datatype".Equals(attrLocal))) + { + continue; + } + else + { + // Ignore all rdf:ID and rdf:datatype attributes. + throw new XMPException("Invalid attribute for literal property element", XMPErrorConstants.Badrdf); + } + } + } + string textValue = string.Empty; + for (int i_1 = 0; i_1 < xmlNode.ChildNodes.Count; i_1++) + { + XmlNode child = xmlNode.ChildNodes.Item(i_1); + if (child.NodeType == XmlNodeType.Text) + { + textValue += child.Value; + } + else + { + throw new XMPException("Invalid child of literal property element", XMPErrorConstants.Badrdf); + } + } + newChild.SetValue(textValue); + } + + /// + /// 7.2.17 parseTypeLiteralPropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( idAttr?, parseLiteral ) ) + /// literal + /// end-element() + /// + /// thown on parsing errors + private static void Rdf_ParseTypeLiteralPropertyElement() + { + throw new XMPException("ParseTypeLiteral property element not allowed", XMPErrorConstants.Badxmp); + } + + /// + /// 7.2.18 parseTypeResourcePropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( idAttr?, parseResource ) ) + /// propertyEltList + /// end-element() + /// Add a new struct node with a qualifier for the possible rdf:ID attribute. + /// + /// + /// 7.2.18 parseTypeResourcePropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( idAttr?, parseResource ) ) + /// propertyEltList + /// end-element() + /// Add a new struct node with a qualifier for the possible rdf:ID attribute. + /// Then process the XML child nodes to get the struct fields. + /// + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_ParseTypeResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) + { + XMPNode newStruct = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); + newStruct.GetOptions().SetStruct(true); + for (int i = 0; i < xmlNode.Attributes.Count; i++) + { + XmlNode attribute = xmlNode.Attributes.Item(i); + if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) + { + continue; + } + string attrLocal = attribute.LocalName; + string attrNS = attribute.NamespaceURI; + if (XMPConstConstants.XmlLang.Equals(attribute.Name)) + { + AddQualifierNode(newStruct, XMPConstConstants.XmlLang, attribute.Value); + } + else + { + if (XMPConstConstants.NsRdf.Equals(attrNS) && ("ID".Equals(attrLocal) || "parseType".Equals(attrLocal))) + { + continue; + } + else + { + // The caller ensured the value is "Resource". + // Ignore all rdf:ID attributes. + throw new XMPException("Invalid attribute for ParseTypeResource property element", XMPErrorConstants.Badrdf); + } + } + } + Rdf_PropertyElementList(xmp, newStruct, xmlNode, false); + if (newStruct.GetHasValueChild()) + { + FixupQualifiedNode(newStruct); + } + } + + /// + /// 7.2.19 parseTypeCollectionPropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( idAttr?, parseCollection ) ) + /// nodeElementList + /// end-element() + /// + /// thown on parsing errors + private static void Rdf_ParseTypeCollectionPropertyElement() + { + throw new XMPException("ParseTypeCollection property element not allowed", XMPErrorConstants.Badxmp); + } + + /// + /// 7.2.20 parseTypeOtherPropertyElt + /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) + /// propertyEltList + /// end-element() + /// + /// thown on parsing errors + private static void Rdf_ParseTypeOtherPropertyElement() + { + throw new XMPException("ParseTypeOther property element not allowed", XMPErrorConstants.Badxmp); + } + + /// + /// 7.2.21 emptyPropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( + /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) + /// end-element() + /// + /// + /// + /// + /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of + /// attributes. + /// + /// + /// 7.2.21 emptyPropertyElt + /// start-element ( URI == propertyElementURIs, + /// attributes == set ( + /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) + /// end-element() + /// + /// + /// + /// + /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of + /// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a + /// simple property with an empty value (ns:Prop1), a simple property whose value is a URI + /// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). + /// An emptyPropertyElt can also represent an XMP struct whose fields are all simple and + /// unqualified (ns:Prop4). + /// It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the + /// verbose form written using a literalPropertyElt. + /// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for + /// design reasons and partly for historical reasons. The XMP mapping rules are: + ///
    + ///
  1. If there is an rdf:value attribute then this is a simple property + /// with a text value. + /// All other attributes are qualifiers. + ///
  2. If there is an rdf:resource attribute then this is a simple property + /// with a URI value. + /// All other attributes are qualifiers. + ///
  3. If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID + /// then this is a simple + /// property with an empty value. + ///
  4. Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, + /// or rdf:nodeID are fields. + ///
+ ///
+ /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Flag if the node is a top-level node + /// thown on parsing errors + private static void Rdf_EmptyPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, bool isTopLevel) + { + bool hasPropertyAttrs = false; + bool hasResourceAttr = false; + bool hasNodeIDAttr = false; + bool hasValueAttr = false; + XmlNode valueNode = null; + // ! Can come from rdf:value or rdf:resource. + if (xmlNode.HasChildNodes) + { + throw new XMPException("Nested content not allowed with rdf:resource or property attributes", XMPErrorConstants.Badrdf); + } + // First figure out what XMP this maps to and remember the XML node for a simple value. + for (int i = 0; i < xmlNode.Attributes.Count; i++) + { + XmlNode attribute = xmlNode.Attributes.Item(i); + if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) + { + continue; + } + int attrTerm = GetRDFTermKind(attribute); + switch (attrTerm) + { + case RdftermId: + { + // Nothing to do. + break; + } + + case RdftermResource: + { + if (hasNodeIDAttr) + { + throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf); + } + else + { + if (hasValueAttr) + { + throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp); + } + } + hasResourceAttr = true; + if (!hasValueAttr) + { + valueNode = attribute; + } + break; + } + + case RdftermNodeId: + { + if (hasResourceAttr) + { + throw new XMPException("Empty property element can't have both rdf:resource and rdf:nodeID", XMPErrorConstants.Badrdf); + } + hasNodeIDAttr = true; + break; + } + + case RdftermOther: + { + if ("value".Equals(attribute.LocalName) && XMPConstConstants.NsRdf.Equals(attribute.NamespaceURI)) + { + if (hasResourceAttr) + { + throw new XMPException("Empty property element can't have both rdf:value and rdf:resource", XMPErrorConstants.Badxmp); + } + hasValueAttr = true; + valueNode = attribute; + } + else + { + if (!XMPConstConstants.XmlLang.Equals(attribute.Name)) + { + hasPropertyAttrs = true; + } + } + break; + } + + default: + { + throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.Badrdf); + } + } + } + // Create the right kind of child node and visit the attributes again + // to add the fields or qualifiers. + // ! Because of implementation vagaries, + // the xmpParent is the tree root for top level properties. + // ! The schema is found, created if necessary, by addChildNode. + XMPNode childNode = AddChildNode(xmp, xmpParent, xmlNode, string.Empty, isTopLevel); + bool childIsStruct = false; + if (hasValueAttr || hasResourceAttr) + { + childNode.SetValue(valueNode != null ? valueNode.Value : string.Empty); + if (!hasValueAttr) + { + // ! Might have both rdf:value and rdf:resource. + childNode.GetOptions().SetURI(true); + } + } + else + { + if (hasPropertyAttrs) + { + childNode.GetOptions().SetStruct(true); + childIsStruct = true; + } + } + for (int i_1 = 0; i_1 < xmlNode.Attributes.Count; i_1++) + { + XmlNode attribute = xmlNode.Attributes.Item(i_1); + if (attribute == valueNode || "xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) + { + continue; + } + // Skip the rdf:value or rdf:resource attribute holding the value. + int attrTerm = GetRDFTermKind(attribute); + switch (attrTerm) + { + case RdftermId: + case RdftermNodeId: + { + break; + } + + case RdftermResource: + { + // Ignore all rdf:ID and rdf:nodeID attributes. + AddQualifierNode(childNode, "rdf:resource", attribute.Value); + break; + } + + case RdftermOther: + { + if (!childIsStruct) + { + AddQualifierNode(childNode, attribute.Name, attribute.Value); + } + else + { + if (XMPConstConstants.XmlLang.Equals(attribute.Name)) + { + AddQualifierNode(childNode, XMPConstConstants.XmlLang, attribute.Value); + } + else + { + AddChildNode(xmp, childNode, attribute, attribute.Value, false); + } + } + break; + } + + default: + { + throw new XMPException("Unrecognized attribute of empty property element", XMPErrorConstants.Badrdf); + } + } + } + } + + /// Adds a child node. + /// the xmp metadata object that is generated + /// the parent xmp node + /// the currently processed XML node + /// Node value + /// Flag if the node is a top-level node + /// Returns the newly created child node. + /// thown on parsing errors + private static XMPNode AddChildNode(XMPMetaImpl xmp, XMPNode xmpParent, XmlNode xmlNode, string value, bool isTopLevel) + { + XMPSchemaRegistry registry = XMPMetaFactory.GetSchemaRegistry(); + string @namespace = xmlNode.NamespaceURI; + string childName; + if (@namespace != null) + { + if (XMPConstConstants.NsDcDeprecated.Equals(@namespace)) + { + // Fix a legacy DC namespace + @namespace = XMPConstConstants.NsDc; + } + string prefix = registry.GetNamespacePrefix(@namespace); + if (prefix == null) + { + prefix = xmlNode.Prefix != null ? xmlNode.Prefix : DefaultPrefix; + prefix = registry.RegisterNamespace(@namespace, prefix); + } + childName = prefix + xmlNode.LocalName; + } + else + { + throw new XMPException("XML namespace required for all elements and attributes", XMPErrorConstants.Badrdf); + } + // create schema node if not already there + PropertyOptions childOptions = new PropertyOptions(); + bool isAlias = false; + if (isTopLevel) + { + // Lookup the schema node, adjust the XMP parent pointer. + // Incoming parent must be the tree root. + XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), @namespace, DefaultPrefix, true); + schemaNode.SetImplicit(false); + // Clear the implicit node bit. + // need runtime check for proper 32 bit code. + xmpParent = schemaNode; + // If this is an alias set the alias flag in the node + // and the hasAliases flag in the tree. + if (registry.FindAlias(childName) != null) + { + isAlias = true; + xmp.GetRoot().SetHasAliases(true); + schemaNode.SetHasAliases(true); + } + } + // Make sure that this is not a duplicate of a named node. + bool isArrayItem = "rdf:li".Equals(childName); + bool isValueNode = "rdf:value".Equals(childName); + // Create XMP node and so some checks + XMPNode newChild = new XMPNode(childName, value, childOptions); + newChild.SetAlias(isAlias); + // Add the new child to the XMP parent node, a value node first. + if (!isValueNode) + { + xmpParent.AddChild(newChild); + } + else + { + xmpParent.AddChild(1, newChild); + } + if (isValueNode) + { + if (isTopLevel || !xmpParent.GetOptions().IsStruct()) + { + throw new XMPException("Misplaced rdf:value element", XMPErrorConstants.Badrdf); + } + xmpParent.SetHasValueChild(true); + } + if (isArrayItem) + { + if (!xmpParent.GetOptions().IsArray()) + { + throw new XMPException("Misplaced rdf:li element", XMPErrorConstants.Badrdf); + } + newChild.SetName(XMPConstConstants.ArrayItemName); + } + return newChild; + } + + /// Adds a qualifier node. + /// the parent xmp node + /// + /// the name of the qualifier which has to be + /// QName including the default prefix + /// + /// the value of the qualifier + /// Returns the newly created child node. + /// thown on parsing errors + private static XMPNode AddQualifierNode(XMPNode xmpParent, string name, string value) + { + bool isLang = XMPConstConstants.XmlLang.Equals(name); + XMPNode newQual = null; + // normalize value of language qualifiers + newQual = new XMPNode(name, isLang ? Utils.NormalizeLangValue(value) : value, null); + xmpParent.AddQualifier(newQual); + return newQual; + } + + /// The parent is an RDF pseudo-struct containing an rdf:value field. + /// + /// The parent is an RDF pseudo-struct containing an rdf:value field. Fix the + /// XMP data model. The rdf:value node must be the first child, the other + /// children are qualifiers. The form, value, and children of the rdf:value + /// node are the real ones. The rdf:value node's qualifiers must be added to + /// the others. + /// + /// the parent xmp node + /// thown on parsing errors + private static void FixupQualifiedNode(XMPNode xmpParent) + { + Debug.Assert(xmpParent.GetOptions().IsStruct() && xmpParent.HasChildren()); + XMPNode valueNode = xmpParent.GetChild(1); + Debug.Assert("rdf:value".Equals(valueNode.GetName())); + // Move the qualifiers on the value node to the parent. + // Make sure an xml:lang qualifier stays at the front. + // Check for duplicate names between the value node's qualifiers and the parent's children. + // The parent's children are about to become qualifiers. Check here, between the groups. + // Intra-group duplicates are caught by XMPNode#addChild(...). + if (valueNode.GetOptions().GetHasLanguage()) + { + if (xmpParent.GetOptions().GetHasLanguage()) + { + throw new XMPException("Redundant xml:lang for rdf:value element", XMPErrorConstants.Badxmp); + } + XMPNode langQual = valueNode.GetQualifier(1); + valueNode.RemoveQualifier(langQual); + xmpParent.AddQualifier(langQual); + } + // Start the remaining copy after the xml:lang qualifier. + for (int i = 1; i <= valueNode.GetQualifierLength(); i++) + { + XMPNode qualifier = valueNode.GetQualifier(i); + xmpParent.AddQualifier(qualifier); + } + // Change the parent's other children into qualifiers. + // This loop starts at 1, child 0 is the rdf:value node. + for (int i_1 = 2; i_1 <= xmpParent.GetChildrenLength(); i_1++) + { + XMPNode qualifier = xmpParent.GetChild(i_1); + xmpParent.AddQualifier(qualifier); + } + // Move the options and value last, other checks need the parent's original options. + // Move the value node's children to be the parent's children. + Debug.Assert(xmpParent.GetOptions().IsStruct() || xmpParent.GetHasValueChild()); + xmpParent.SetHasValueChild(false); + xmpParent.GetOptions().SetStruct(false); + xmpParent.GetOptions().MergeWith(valueNode.GetOptions()); + xmpParent.SetValue(valueNode.GetValue()); + xmpParent.RemoveChildren(); + for (Iterator it = valueNode.IterateChildren(); it.HasNext(); ) + { + XMPNode child = (XMPNode)it.Next(); + xmpParent.AddChild(child); + } + } + + /// Checks if the node is a white space. + /// an XML-node + /// + /// Returns whether the node is a whitespace node, + /// i.e. a text node that contains only whitespaces. + /// + private static bool IsWhitespaceNode(XmlNode node) + { + if (node.NodeType != XmlNodeType.Text) + { + return false; + } + string value = node.Value; + for (int i = 0; i < value.Length; i++) + { + if (!char.IsWhiteSpace(value[i])) + { + return false; + } + } + return true; + } + + /// + /// 7.2.6 propertyElementURIs + /// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms ) + /// + /// the term id + /// Return true if the term is a property element name. + private static bool IsPropertyElementName(int term) + { + if (term == RdftermDescription || IsOldTerm(term)) + { + return false; + } + else + { + return (!IsCoreSyntaxTerm(term)); + } + } + + /// + /// 7.2.4 oldTerms
+ /// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID + ///
+ /// the term id + /// Returns true if the term is an old term. + private static bool IsOldTerm(int term) + { + return RdftermFirstOld <= term && term <= RdftermLastOld; + } + + /// + /// 7.2.2 coreSyntaxTerms
+ /// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | + /// rdf:datatype + ///
+ /// the term id + /// Return true if the term is a core syntax term + private static bool IsCoreSyntaxTerm(int term) + { + return RdftermFirstCore <= term && term <= RdftermLastCore; + } + + /// Determines the ID for a certain RDF Term. + /// + /// Determines the ID for a certain RDF Term. + /// Arranged to hopefully minimize the parse time for large XMP. + /// + /// an XML node + /// Returns the term ID. + private static int GetRDFTermKind(XmlNode node) + { + string localName = node.LocalName; + string @namespace = node.NamespaceURI; + if (@namespace == null && ("about".Equals(localName) || "ID".Equals(localName)) && (node is XmlAttribute) && XMPConstConstants.NsRdf.Equals(((XmlAttribute)node).OwnerElement.NamespaceURI)) + { + @namespace = XMPConstConstants.NsRdf; + } + if (XMPConstConstants.NsRdf.Equals(@namespace)) + { + if ("li".Equals(localName)) + { + return RdftermLi; + } + else + { + if ("parseType".Equals(localName)) + { + return RdftermParseType; + } + else + { + if ("Description".Equals(localName)) + { + return RdftermDescription; + } + else + { + if ("about".Equals(localName)) + { + return RdftermAbout; + } + else + { + if ("resource".Equals(localName)) + { + return RdftermResource; + } + else + { + if ("RDF".Equals(localName)) + { + return RdftermRdf; + } + else + { + if ("ID".Equals(localName)) + { + return RdftermId; + } + else + { + if ("nodeID".Equals(localName)) + { + return RdftermNodeId; + } + else + { + if ("datatype".Equals(localName)) + { + return RdftermDatatype; + } + else + { + if ("aboutEach".Equals(localName)) + { + return RdftermAboutEach; + } + else + { + if ("aboutEachPrefix".Equals(localName)) + { + return RdftermAboutEachPrefix; + } + else + { + if ("bagID".Equals(localName)) + { + return RdftermBagId; + } + } + } + } + } + } + } + } + } + } + } + } + } + return RdftermOther; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/QName.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/QName.cs index a6b837243..6a4f59277 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/QName.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/QName.cs @@ -1,67 +1,67 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// 09.11.2006 - public class QName - { - /// XML namespace prefix - private string prefix; - - /// XML localname - private string localName; - - /// Splits a qname into prefix and localname. - /// a QName - public QName(string qname) - { - int colon = qname.IndexOf(':'); - if (colon >= 0) - { - prefix = Runtime.Substring(qname, 0, colon); - localName = Runtime.Substring(qname, colon + 1); - } - else - { - prefix = string.Empty; - localName = qname; - } - } - - /// Constructor that initializes the fields - /// the prefix - /// the name - public QName(string prefix, string localName) - { - this.prefix = prefix; - this.localName = localName; - } - - /// Returns whether the QName has a prefix. - public virtual bool HasPrefix() - { - return prefix != null && prefix.Length > 0; - } - - /// the localName - public virtual string GetLocalName() - { - return localName; - } - - /// the prefix - public virtual string GetPrefix() - { - return prefix; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// 09.11.2006 + public class QName + { + /// XML namespace prefix + private string prefix; + + /// XML localname + private string localName; + + /// Splits a qname into prefix and localname. + /// a QName + public QName(string qname) + { + int colon = qname.IndexOf(':'); + if (colon >= 0) + { + prefix = Runtime.Substring(qname, 0, colon); + localName = Runtime.Substring(qname, colon + 1); + } + else + { + prefix = string.Empty; + localName = qname; + } + } + + /// Constructor that initializes the fields + /// the prefix + /// the name + public QName(string prefix, string localName) + { + this.prefix = prefix; + this.localName = localName; + } + + /// Returns whether the QName has a prefix. + public virtual bool HasPrefix() + { + return prefix != null && prefix.Length > 0; + } + + /// the localName + public virtual string GetLocalName() + { + return localName; + } + + /// the prefix + public virtual string GetPrefix() + { + return prefix; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/Utils.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/Utils.cs index e99e778b3..bfb738306 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/Utils.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/Utils.cs @@ -1,536 +1,536 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using System.Text; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// Utility functions for the XMPToolkit implementation. - /// 06.06.2006 - public class Utils : XMPConst - { - /// segments of a UUID - public const int UuidSegmentCount = 4; - - /// length of a UUID - public const int UuidLength = 32 + UuidSegmentCount; - - /// table of XML name start chars (<= 0xFF) - private static bool[] xmlNameStartChars; - - /// table of XML name chars (<= 0xFF) - private static bool[] xmlNameChars; - - static Utils() - { - InitCharTables(); - } - - /// Private constructor - private Utils() - { - } - - // EMPTY - /// - /// Normalize an xml:lang value so that comparisons are effectively case - /// insensitive as required by RFC 3066 (which superceeds RFC 1766). - /// - /// - /// Normalize an xml:lang value so that comparisons are effectively case - /// insensitive as required by RFC 3066 (which superceeds RFC 1766). The - /// normalization rules: - ///
    - ///
  • The primary subtag is lower case, the suggested practice of ISO 639. - ///
  • All 2 letter secondary subtags are upper case, the suggested - /// practice of ISO 3166. - ///
  • All other subtags are lower case. - ///
- ///
- /// raw value - /// Returns the normalized value. - public static string NormalizeLangValue(string value) - { - // don't normalize x-default - if (XMPConstConstants.XDefault.Equals(value)) - { - return value; - } - int subTag = 1; - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < value.Length; i++) - { - switch (value[i]) - { - case '-': - case '_': - { - // move to next subtag and convert underscore to hyphen - buffer.Append('-'); - subTag++; - break; - } - - case ' ': - { - // remove spaces - break; - } - - default: - { - // convert second subtag to uppercase, all other to lowercase - if (subTag != 2) - { - buffer.Append(Char.ToLower(value[i])); - } - else - { - buffer.Append(Char.ToUpper(value[i])); - } - break; - } - } - } - return buffer.ToString(); - } - - /// - /// Split the name and value parts for field and qualifier selectors: - ///
    - ///
  • [qualName="value"] - An element in an array of structs, chosen by a - /// field value. - ///
- /// - /// Split the name and value parts for field and qualifier selectors: - ///
    - ///
  • [qualName="value"] - An element in an array of structs, chosen by a - /// field value. - ///
  • [?qualName="value"] - An element in an array, chosen by a qualifier - /// value. - ///
- /// The value portion is a string quoted by ''' or '"'. The value may contain - /// any character including a doubled quoting character. The value may be - /// empty. Note: It is assumed that the expression is formal - /// correct - ///
- /// the selector - /// - /// Returns an array where the first entry contains the name and the - /// second the value. - /// - internal static string[] SplitNameAndValue(string selector) - { - // get the name - int eq = selector.IndexOf('='); - int pos = 1; - if (selector[pos] == '?') - { - pos++; - } - string name = Runtime.Substring(selector, pos, eq); - // get the value - pos = eq + 1; - char quote = selector[pos]; - pos++; - int end = selector.Length - 2; - // quote and ] - StringBuilder value = new StringBuilder(end - eq); - while (pos < end) - { - value.Append(selector[pos]); - pos++; - if (selector[pos] == quote) - { - // skip one quote in value - pos++; - } - } - return new string[] { name, value.ToString() }; - } - - /// a schema namespace - /// an XMP Property - /// - /// Returns true if the property is defined as "Internal - /// Property", see XMP Specification. - /// - internal static bool IsInternalProperty(string schema, string prop) - { - bool isInternal = false; - if (XMPConstConstants.NsDc.Equals(schema)) - { - if ("dc:format".Equals(prop) || "dc:language".Equals(prop)) - { - isInternal = true; - } - } - else - { - if (XMPConstConstants.NsXmp.Equals(schema)) - { - if ("xmp:BaseURL".Equals(prop) || "xmp:CreatorTool".Equals(prop) || "xmp:Format".Equals(prop) || "xmp:Locale".Equals(prop) || "xmp:MetadataDate".Equals(prop) || "xmp:ModifyDate".Equals(prop)) - { - isInternal = true; - } - } - else - { - if (XMPConstConstants.NsPdf.Equals(schema)) - { - if ("pdf:BaseURL".Equals(prop) || "pdf:Creator".Equals(prop) || "pdf:ModDate".Equals(prop) || "pdf:PDFVersion".Equals(prop) || "pdf:Producer".Equals(prop)) - { - isInternal = true; - } - } - else - { - if (XMPConstConstants.NsTiff.Equals(schema)) - { - isInternal = true; - if ("tiff:ImageDescription".Equals(prop) || "tiff:Artist".Equals(prop) || "tiff:Copyright".Equals(prop)) - { - isInternal = false; - } - } - else - { - if (XMPConstConstants.NsExif.Equals(schema)) - { - isInternal = true; - if ("exif:UserComment".Equals(prop)) - { - isInternal = false; - } - } - else - { - if (XMPConstConstants.NsExifAux.Equals(schema)) - { - isInternal = true; - } - else - { - if (XMPConstConstants.NsPhotoshop.Equals(schema)) - { - if ("photoshop:ICCProfile".Equals(prop)) - { - isInternal = true; - } - } - else - { - if (XMPConstConstants.NsCameraraw.Equals(schema)) - { - if ("crs:Version".Equals(prop) || "crs:RawFileName".Equals(prop) || "crs:ToneCurveName".Equals(prop)) - { - isInternal = true; - } - } - else - { - if (XMPConstConstants.NsAdobestockphoto.Equals(schema)) - { - isInternal = true; - } - else - { - if (XMPConstConstants.NsXmpMm.Equals(schema)) - { - isInternal = true; - } - else - { - if (XMPConstConstants.TypeText.Equals(schema)) - { - isInternal = true; - } - else - { - if (XMPConstConstants.TypePagedfile.Equals(schema)) - { - isInternal = true; - } - else - { - if (XMPConstConstants.TypeGraphics.Equals(schema)) - { - isInternal = true; - } - else - { - if (XMPConstConstants.TypeImage.Equals(schema)) - { - isInternal = true; - } - else - { - if (XMPConstConstants.TypeFont.Equals(schema)) - { - isInternal = true; - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - return isInternal; - } - - /// - /// Check some requirements for an UUID: - ///
    - ///
  • Length of the UUID is 32
  • - ///
  • The Delimiter count is 4 and all the 4 delimiter are on their right - /// position (8,13,18,23)
  • - ///
- ///
- /// uuid to test - /// true - this is a well formed UUID, false - UUID has not the expected format - internal static bool CheckUUIDFormat(string uuid) - { - bool result = true; - int delimCnt = 0; - int delimPos = 0; - if (uuid == null) - { - return false; - } - for (delimPos = 0; delimPos < uuid.Length; delimPos++) - { - if (uuid[delimPos] == '-') - { - delimCnt++; - result = result && (delimPos == 8 || delimPos == 13 || delimPos == 18 || delimPos == 23); - } - } - return result && UuidSegmentCount == delimCnt && UuidLength == delimPos; - } - - /// Simple check for valid XMLNames. - /// - /// Simple check for valid XMLNames. Within ASCII range
- /// ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]
- /// are accepted, above all characters (which is not entirely - /// correct according to the XML Spec. - ///
- /// an XML Name - /// Return true if the name is correct. - public static bool IsXMLName(string name) - { - if (name.Length > 0 && !IsNameStartChar(name[0])) - { - return false; - } - for (int i = 1; i < name.Length; i++) - { - if (!IsNameChar(name[i])) - { - return false; - } - } - return true; - } - - /// - /// Checks if the value is a legal "unqualified" XML name, as - /// defined in the XML Namespaces proposed recommendation. - /// - /// - /// Checks if the value is a legal "unqualified" XML name, as - /// defined in the XML Namespaces proposed recommendation. - /// These are XML names, except that they must not contain a colon. - /// - /// the value to check - /// Returns true if the name is a valid "unqualified" XML name. - public static bool IsXMLNameNS(string name) - { - if (name.Length > 0 && (!IsNameStartChar(name[0]) || name[0] == ':')) - { - return false; - } - for (int i = 1; i < name.Length; i++) - { - if (!IsNameChar(name[i]) || name[i] == ':') - { - return false; - } - } - return true; - } - - /// a char - /// Returns true if the char is an ASCII control char. - internal static bool IsControlChar(char c) - { - return (c <= unchecked((int)(0x1F)) || c == unchecked((int)(0x7F))) && c != unchecked((int)(0x09)) && c != unchecked((int)(0x0A)) && c != unchecked((int)(0x0D)); - } - - /// Serializes the node value in XML encoding. - /// - /// Serializes the node value in XML encoding. Its used for tag bodies and - /// attributes.
- /// Note: The attribute is always limited by quotes, - /// thats why &apos; is never serialized.
- /// Note: Control chars are written unescaped, but if the user uses others than tab, LF - /// and CR the resulting XML will become invalid. - ///
- /// a string - /// flag if string is attribute value (need to additional escape quotes) - /// Decides if LF, CR and TAB are escaped. - /// Returns the value ready for XML output. - public static string EscapeXML(string value, bool forAttribute, bool escapeWhitespaces) - { - // quick check if character are contained that need special treatment - bool needsEscaping = false; - for (int i = 0; i < value.Length; i++) - { - char c = value[i]; - if (c == '<' || c == '>' || c == '&' || (escapeWhitespaces && (c == '\t' || c == '\n' || c == '\r')) || (forAttribute && c == '"')) - { - // XML chars - needsEscaping = true; - break; - } - } - if (!needsEscaping) - { - // fast path - return value; - } - else - { - // slow path with escaping - StringBuilder buffer = new StringBuilder(value.Length * 4 / 3); - for (int i_1 = 0; i_1 < value.Length; i_1++) - { - char c = value[i_1]; - if (!(escapeWhitespaces && (c == '\t' || c == '\n' || c == '\r'))) - { - switch (c) - { - case '<': - { - // we do what "Canonical XML" expects - // AUDIT: ' not serialized as only outer qoutes are used - buffer.Append("<"); - continue; - } - - case '>': - { - buffer.Append(">"); - continue; - } - - case '&': - { - buffer.Append("&"); - continue; - } - - case '"': - { - buffer.Append(forAttribute ? """ : "\""); - continue; - } - - default: - { - buffer.Append(c); - continue; - } - } - } - else - { - // write control chars escaped, - // if there are others than tab, LF and CR the xml will become invalid. - buffer.Append("&#x"); - buffer.Append(Extensions.ToHexString(c).ToUpper()); - buffer.Append(';'); - } - } - return buffer.ToString(); - } - } - - /// Replaces the ASCII control chars with a space. - /// a node value - /// Returns the cleaned up value - internal static string RemoveControlChars(string value) - { - StringBuilder buffer = new StringBuilder(value); - for (int i = 0; i < buffer.Length; i++) - { - if (IsControlChar(buffer[i])) - { - Runtime.SetCharAt(buffer, i, ' '); - } - } - return buffer.ToString(); - } - - /// Simple check if a character is a valid XML start name char. - /// - /// Simple check if a character is a valid XML start name char. - /// All characters according to the XML Spec 1.1 are accepted: - /// http://www.w3.org/TR/xml11/#NT-NameStartChar - /// - /// a character - /// Returns true if the character is a valid first char of an XML name. - private static bool IsNameStartChar(char ch) - { - return (ch <= unchecked((int)(0xFF)) && xmlNameStartChars[ch]) || (ch >= unchecked((int)(0x100)) && ch <= unchecked((int)(0x2FF))) || (ch >= unchecked((int)(0x370)) && ch <= unchecked((int)(0x37D))) || (ch >= unchecked((int)(0x37F)) && ch <= - unchecked((int)(0x1FFF))) || (ch >= unchecked((int)(0x200C)) && ch <= unchecked((int)(0x200D))) || (ch >= unchecked((int)(0x2070)) && ch <= unchecked((int)(0x218F))) || (ch >= unchecked((int)(0x2C00)) && ch <= unchecked((int)(0x2FEF))) || - (ch >= unchecked((int)(0x3001)) && ch <= unchecked((int)(0xD7FF))) || (ch >= unchecked((int)(0xF900)) && ch <= unchecked((int)(0xFDCF))) || (ch >= unchecked((int)(0xFDF0)) && ch <= unchecked((int)(0xFFFD))) || (ch >= unchecked((int)(0x10000 - )) && ch <= unchecked((int)(0xEFFFF))); - } - - /// - /// Simple check if a character is a valid XML name char - /// (every char except the first one), according to the XML Spec 1.1: - /// http://www.w3.org/TR/xml11/#NT-NameChar - /// - /// a character - /// Returns true if the character is a valid char of an XML name. - private static bool IsNameChar(char ch) - { - return (ch <= unchecked((int)(0xFF)) && xmlNameChars[ch]) || IsNameStartChar(ch) || (ch >= unchecked((int)(0x300)) && ch <= unchecked((int)(0x36F))) || (ch >= unchecked((int)(0x203F)) && ch <= unchecked((int)(0x2040))); - } - - /// - /// Initializes the char tables for the chars 0x00-0xFF for later use, - /// according to the XML 1.1 specification - /// http://www.w3.org/TR/xml11 - /// - private static void InitCharTables() - { - xmlNameChars = new bool[unchecked((int)(0x0100))]; - xmlNameStartChars = new bool[unchecked((int)(0x0100))]; - for (char ch = (char)0; ch < xmlNameChars.Length; ch++) - { - xmlNameStartChars[ch] = ch == ':' || ('A' <= ch && ch <= 'Z') || ch == '_' || ('a' <= ch && ch <= 'z') || (unchecked((int)(0xC0)) <= ch && ch <= unchecked((int)(0xD6))) || (unchecked((int)(0xD8)) <= ch && ch <= unchecked((int)(0xF6))) || (unchecked( - (int)(0xF8)) <= ch && ch <= unchecked((int)(0xFF))); - xmlNameChars[ch] = xmlNameStartChars[ch] || ch == '-' || ch == '.' || ('0' <= ch && ch <= '9') || ch == unchecked((int)(0xB7)); - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using System.Text; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// Utility functions for the XMPToolkit implementation. + /// 06.06.2006 + public class Utils : XMPConst + { + /// segments of a UUID + public const int UuidSegmentCount = 4; + + /// length of a UUID + public const int UuidLength = 32 + UuidSegmentCount; + + /// table of XML name start chars (<= 0xFF) + private static bool[] xmlNameStartChars; + + /// table of XML name chars (<= 0xFF) + private static bool[] xmlNameChars; + + static Utils() + { + InitCharTables(); + } + + /// Private constructor + private Utils() + { + } + + // EMPTY + /// + /// Normalize an xml:lang value so that comparisons are effectively case + /// insensitive as required by RFC 3066 (which superceeds RFC 1766). + /// + /// + /// Normalize an xml:lang value so that comparisons are effectively case + /// insensitive as required by RFC 3066 (which superceeds RFC 1766). The + /// normalization rules: + ///
    + ///
  • The primary subtag is lower case, the suggested practice of ISO 639. + ///
  • All 2 letter secondary subtags are upper case, the suggested + /// practice of ISO 3166. + ///
  • All other subtags are lower case. + ///
+ ///
+ /// raw value + /// Returns the normalized value. + public static string NormalizeLangValue(string value) + { + // don't normalize x-default + if (XMPConstConstants.XDefault.Equals(value)) + { + return value; + } + int subTag = 1; + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < value.Length; i++) + { + switch (value[i]) + { + case '-': + case '_': + { + // move to next subtag and convert underscore to hyphen + buffer.Append('-'); + subTag++; + break; + } + + case ' ': + { + // remove spaces + break; + } + + default: + { + // convert second subtag to uppercase, all other to lowercase + if (subTag != 2) + { + buffer.Append(Char.ToLower(value[i])); + } + else + { + buffer.Append(Char.ToUpper(value[i])); + } + break; + } + } + } + return buffer.ToString(); + } + + /// + /// Split the name and value parts for field and qualifier selectors: + ///
    + ///
  • [qualName="value"] - An element in an array of structs, chosen by a + /// field value. + ///
+ /// + /// Split the name and value parts for field and qualifier selectors: + ///
    + ///
  • [qualName="value"] - An element in an array of structs, chosen by a + /// field value. + ///
  • [?qualName="value"] - An element in an array, chosen by a qualifier + /// value. + ///
+ /// The value portion is a string quoted by ''' or '"'. The value may contain + /// any character including a doubled quoting character. The value may be + /// empty. Note: It is assumed that the expression is formal + /// correct + ///
+ /// the selector + /// + /// Returns an array where the first entry contains the name and the + /// second the value. + /// + internal static string[] SplitNameAndValue(string selector) + { + // get the name + int eq = selector.IndexOf('='); + int pos = 1; + if (selector[pos] == '?') + { + pos++; + } + string name = Runtime.Substring(selector, pos, eq); + // get the value + pos = eq + 1; + char quote = selector[pos]; + pos++; + int end = selector.Length - 2; + // quote and ] + StringBuilder value = new StringBuilder(end - eq); + while (pos < end) + { + value.Append(selector[pos]); + pos++; + if (selector[pos] == quote) + { + // skip one quote in value + pos++; + } + } + return new string[] { name, value.ToString() }; + } + + /// a schema namespace + /// an XMP Property + /// + /// Returns true if the property is defined as "Internal + /// Property", see XMP Specification. + /// + internal static bool IsInternalProperty(string schema, string prop) + { + bool isInternal = false; + if (XMPConstConstants.NsDc.Equals(schema)) + { + if ("dc:format".Equals(prop) || "dc:language".Equals(prop)) + { + isInternal = true; + } + } + else + { + if (XMPConstConstants.NsXmp.Equals(schema)) + { + if ("xmp:BaseURL".Equals(prop) || "xmp:CreatorTool".Equals(prop) || "xmp:Format".Equals(prop) || "xmp:Locale".Equals(prop) || "xmp:MetadataDate".Equals(prop) || "xmp:ModifyDate".Equals(prop)) + { + isInternal = true; + } + } + else + { + if (XMPConstConstants.NsPdf.Equals(schema)) + { + if ("pdf:BaseURL".Equals(prop) || "pdf:Creator".Equals(prop) || "pdf:ModDate".Equals(prop) || "pdf:PDFVersion".Equals(prop) || "pdf:Producer".Equals(prop)) + { + isInternal = true; + } + } + else + { + if (XMPConstConstants.NsTiff.Equals(schema)) + { + isInternal = true; + if ("tiff:ImageDescription".Equals(prop) || "tiff:Artist".Equals(prop) || "tiff:Copyright".Equals(prop)) + { + isInternal = false; + } + } + else + { + if (XMPConstConstants.NsExif.Equals(schema)) + { + isInternal = true; + if ("exif:UserComment".Equals(prop)) + { + isInternal = false; + } + } + else + { + if (XMPConstConstants.NsExifAux.Equals(schema)) + { + isInternal = true; + } + else + { + if (XMPConstConstants.NsPhotoshop.Equals(schema)) + { + if ("photoshop:ICCProfile".Equals(prop)) + { + isInternal = true; + } + } + else + { + if (XMPConstConstants.NsCameraraw.Equals(schema)) + { + if ("crs:Version".Equals(prop) || "crs:RawFileName".Equals(prop) || "crs:ToneCurveName".Equals(prop)) + { + isInternal = true; + } + } + else + { + if (XMPConstConstants.NsAdobestockphoto.Equals(schema)) + { + isInternal = true; + } + else + { + if (XMPConstConstants.NsXmpMm.Equals(schema)) + { + isInternal = true; + } + else + { + if (XMPConstConstants.TypeText.Equals(schema)) + { + isInternal = true; + } + else + { + if (XMPConstConstants.TypePagedfile.Equals(schema)) + { + isInternal = true; + } + else + { + if (XMPConstConstants.TypeGraphics.Equals(schema)) + { + isInternal = true; + } + else + { + if (XMPConstConstants.TypeImage.Equals(schema)) + { + isInternal = true; + } + else + { + if (XMPConstConstants.TypeFont.Equals(schema)) + { + isInternal = true; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return isInternal; + } + + /// + /// Check some requirements for an UUID: + ///
    + ///
  • Length of the UUID is 32
  • + ///
  • The Delimiter count is 4 and all the 4 delimiter are on their right + /// position (8,13,18,23)
  • + ///
+ ///
+ /// uuid to test + /// true - this is a well formed UUID, false - UUID has not the expected format + internal static bool CheckUUIDFormat(string uuid) + { + bool result = true; + int delimCnt = 0; + int delimPos = 0; + if (uuid == null) + { + return false; + } + for (delimPos = 0; delimPos < uuid.Length; delimPos++) + { + if (uuid[delimPos] == '-') + { + delimCnt++; + result = result && (delimPos == 8 || delimPos == 13 || delimPos == 18 || delimPos == 23); + } + } + return result && UuidSegmentCount == delimCnt && UuidLength == delimPos; + } + + /// Simple check for valid XMLNames. + /// + /// Simple check for valid XMLNames. Within ASCII range
+ /// ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]
+ /// are accepted, above all characters (which is not entirely + /// correct according to the XML Spec. + ///
+ /// an XML Name + /// Return true if the name is correct. + public static bool IsXMLName(string name) + { + if (name.Length > 0 && !IsNameStartChar(name[0])) + { + return false; + } + for (int i = 1; i < name.Length; i++) + { + if (!IsNameChar(name[i])) + { + return false; + } + } + return true; + } + + /// + /// Checks if the value is a legal "unqualified" XML name, as + /// defined in the XML Namespaces proposed recommendation. + /// + /// + /// Checks if the value is a legal "unqualified" XML name, as + /// defined in the XML Namespaces proposed recommendation. + /// These are XML names, except that they must not contain a colon. + /// + /// the value to check + /// Returns true if the name is a valid "unqualified" XML name. + public static bool IsXMLNameNS(string name) + { + if (name.Length > 0 && (!IsNameStartChar(name[0]) || name[0] == ':')) + { + return false; + } + for (int i = 1; i < name.Length; i++) + { + if (!IsNameChar(name[i]) || name[i] == ':') + { + return false; + } + } + return true; + } + + /// a char + /// Returns true if the char is an ASCII control char. + internal static bool IsControlChar(char c) + { + return (c <= unchecked((int)(0x1F)) || c == unchecked((int)(0x7F))) && c != unchecked((int)(0x09)) && c != unchecked((int)(0x0A)) && c != unchecked((int)(0x0D)); + } + + /// Serializes the node value in XML encoding. + /// + /// Serializes the node value in XML encoding. Its used for tag bodies and + /// attributes.
+ /// Note: The attribute is always limited by quotes, + /// thats why &apos; is never serialized.
+ /// Note: Control chars are written unescaped, but if the user uses others than tab, LF + /// and CR the resulting XML will become invalid. + ///
+ /// a string + /// flag if string is attribute value (need to additional escape quotes) + /// Decides if LF, CR and TAB are escaped. + /// Returns the value ready for XML output. + public static string EscapeXML(string value, bool forAttribute, bool escapeWhitespaces) + { + // quick check if character are contained that need special treatment + bool needsEscaping = false; + for (int i = 0; i < value.Length; i++) + { + char c = value[i]; + if (c == '<' || c == '>' || c == '&' || (escapeWhitespaces && (c == '\t' || c == '\n' || c == '\r')) || (forAttribute && c == '"')) + { + // XML chars + needsEscaping = true; + break; + } + } + if (!needsEscaping) + { + // fast path + return value; + } + else + { + // slow path with escaping + StringBuilder buffer = new StringBuilder(value.Length * 4 / 3); + for (int i_1 = 0; i_1 < value.Length; i_1++) + { + char c = value[i_1]; + if (!(escapeWhitespaces && (c == '\t' || c == '\n' || c == '\r'))) + { + switch (c) + { + case '<': + { + // we do what "Canonical XML" expects + // AUDIT: ' not serialized as only outer qoutes are used + buffer.Append("<"); + continue; + } + + case '>': + { + buffer.Append(">"); + continue; + } + + case '&': + { + buffer.Append("&"); + continue; + } + + case '"': + { + buffer.Append(forAttribute ? """ : "\""); + continue; + } + + default: + { + buffer.Append(c); + continue; + } + } + } + else + { + // write control chars escaped, + // if there are others than tab, LF and CR the xml will become invalid. + buffer.Append("&#x"); + buffer.Append(Extensions.ToHexString(c).ToUpper()); + buffer.Append(';'); + } + } + return buffer.ToString(); + } + } + + /// Replaces the ASCII control chars with a space. + /// a node value + /// Returns the cleaned up value + internal static string RemoveControlChars(string value) + { + StringBuilder buffer = new StringBuilder(value); + for (int i = 0; i < buffer.Length; i++) + { + if (IsControlChar(buffer[i])) + { + Runtime.SetCharAt(buffer, i, ' '); + } + } + return buffer.ToString(); + } + + /// Simple check if a character is a valid XML start name char. + /// + /// Simple check if a character is a valid XML start name char. + /// All characters according to the XML Spec 1.1 are accepted: + /// http://www.w3.org/TR/xml11/#NT-NameStartChar + /// + /// a character + /// Returns true if the character is a valid first char of an XML name. + private static bool IsNameStartChar(char ch) + { + return (ch <= unchecked((int)(0xFF)) && xmlNameStartChars[ch]) || (ch >= unchecked((int)(0x100)) && ch <= unchecked((int)(0x2FF))) || (ch >= unchecked((int)(0x370)) && ch <= unchecked((int)(0x37D))) || (ch >= unchecked((int)(0x37F)) && ch <= + unchecked((int)(0x1FFF))) || (ch >= unchecked((int)(0x200C)) && ch <= unchecked((int)(0x200D))) || (ch >= unchecked((int)(0x2070)) && ch <= unchecked((int)(0x218F))) || (ch >= unchecked((int)(0x2C00)) && ch <= unchecked((int)(0x2FEF))) || + (ch >= unchecked((int)(0x3001)) && ch <= unchecked((int)(0xD7FF))) || (ch >= unchecked((int)(0xF900)) && ch <= unchecked((int)(0xFDCF))) || (ch >= unchecked((int)(0xFDF0)) && ch <= unchecked((int)(0xFFFD))) || (ch >= unchecked((int)(0x10000 + )) && ch <= unchecked((int)(0xEFFFF))); + } + + /// + /// Simple check if a character is a valid XML name char + /// (every char except the first one), according to the XML Spec 1.1: + /// http://www.w3.org/TR/xml11/#NT-NameChar + /// + /// a character + /// Returns true if the character is a valid char of an XML name. + private static bool IsNameChar(char ch) + { + return (ch <= unchecked((int)(0xFF)) && xmlNameChars[ch]) || IsNameStartChar(ch) || (ch >= unchecked((int)(0x300)) && ch <= unchecked((int)(0x36F))) || (ch >= unchecked((int)(0x203F)) && ch <= unchecked((int)(0x2040))); + } + + /// + /// Initializes the char tables for the chars 0x00-0xFF for later use, + /// according to the XML 1.1 specification + /// http://www.w3.org/TR/xml11 + /// + private static void InitCharTables() + { + xmlNameChars = new bool[unchecked((int)(0x0100))]; + xmlNameStartChars = new bool[unchecked((int)(0x0100))]; + for (char ch = (char)0; ch < xmlNameChars.Length; ch++) + { + xmlNameStartChars[ch] = ch == ':' || ('A' <= ch && ch <= 'Z') || ch == '_' || ('a' <= ch && ch <= 'z') || (unchecked((int)(0xC0)) <= ch && ch <= unchecked((int)(0xD6))) || (unchecked((int)(0xD8)) <= ch && ch <= unchecked((int)(0xF6))) || (unchecked( + (int)(0xF8)) <= ch && ch <= unchecked((int)(0xFF))); + xmlNameChars[ch] = xmlNameStartChars[ch] || ch == '-' || ch == '.' || ('0' <= ch && ch <= '9') || ch == unchecked((int)(0xB7)); + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPDateTimeImpl.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPDateTimeImpl.cs index f496f5862..1cbe5b8ba 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPDateTimeImpl.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPDateTimeImpl.cs @@ -1,315 +1,315 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using System.Globalization; -using Sharpen; -using Calendar = Sharpen.Calendar; -using GregorianCalendar = Sharpen.GregorianCalendar; - -namespace Com.Adobe.Xmp.Impl -{ - /// The implementation of XMPDateTime. - /// - /// The implementation of XMPDateTime. Internally a calendar is used - /// plus an additional nano seconds field, because Calendar supports only milli - /// seconds. The nanoSeconds convers only the resolution beyond a milli second. - /// - /// 16.02.2006 - public class XMPDateTimeImpl : XMPDateTime - { - private int year = 0; - - private int month = 0; - - private int day = 0; - - private int hour = 0; - - private int minute = 0; - - private int second = 0; - - /// Use NO time zone as default - private TimeZoneInfo timeZone = null; - - /// The nano seconds take micro and nano seconds, while the milli seconds are in the calendar. - private int nanoSeconds; - - private bool hasDate = false; - - private bool hasTime = false; - - private bool hasTimeZone = false; - - /// - /// Creates an XMPDateTime-instance with the current time in the default time - /// zone. - /// - public XMPDateTimeImpl() - { - } - - /// Creates an XMPDateTime-instance from a calendar. - /// a Calendar - public XMPDateTimeImpl(Calendar calendar) - { - // EMPTY - // extract the date and timezone from the calendar provided - DateTime date = calendar.GetTime(); - TimeZoneInfo zone = calendar.GetTimeZone(); - // put that date into a calendar the pretty much represents ISO8601 - // I use US because it is close to the "locale" for the ISO8601 spec - GregorianCalendar intCalendar = (GregorianCalendar)Calendar.GetInstance(CultureInfo.InvariantCulture); - intCalendar.SetGregorianChange(Extensions.CreateDate(long.MinValue)); - intCalendar.SetTimeZone(zone); - intCalendar.SetTime(date); - this.year = intCalendar.Get(CalendarEnum.Year); - this.month = intCalendar.Get(CalendarEnum.Month) + 1; - // cal is from 0..12 - this.day = intCalendar.Get(CalendarEnum.DayOfMonth); - this.hour = intCalendar.Get(CalendarEnum.HourOfDay); - this.minute = intCalendar.Get(CalendarEnum.Minute); - this.second = intCalendar.Get(CalendarEnum.Second); - this.nanoSeconds = intCalendar.Get(CalendarEnum.Millisecond) * 1000000; - this.timeZone = intCalendar.GetTimeZone(); - // object contains all date components - hasDate = hasTime = hasTimeZone = true; - } - - /// - /// Creates an XMPDateTime-instance from - /// a Date and a TimeZone. - /// - /// a date describing an absolute point in time - /// a TimeZone how to interpret the date - public XMPDateTimeImpl(DateTime date, TimeZoneInfo timeZone) - { - GregorianCalendar calendar = new GregorianCalendar(timeZone); - calendar.SetTime(date); - this.year = calendar.Get(CalendarEnum.Year); - this.month = calendar.Get(CalendarEnum.Month) + 1; - // cal is from 0..12 - this.day = calendar.Get(CalendarEnum.DayOfMonth); - this.hour = calendar.Get(CalendarEnum.HourOfDay); - this.minute = calendar.Get(CalendarEnum.Minute); - this.second = calendar.Get(CalendarEnum.Second); - this.nanoSeconds = calendar.Get(CalendarEnum.Millisecond) * 1000000; - this.timeZone = timeZone; - // object contains all date components - hasDate = hasTime = hasTimeZone = true; - } - - /// Creates an XMPDateTime-instance from an ISO 8601 string. - /// an ISO 8601 string - /// If the string is a non-conform ISO 8601 string, an exception is thrown - public XMPDateTimeImpl(string strValue) - { - ISO8601Converter.Parse(strValue, this); - } - - /// - public virtual int GetYear() - { - return year; - } - - /// - public virtual void SetYear(int year) - { - this.year = Math.Min(Math.Abs(year), 9999); - this.hasDate = true; - } - - /// - public virtual int GetMonth() - { - return month; - } - - /// - public virtual void SetMonth(int month) - { - if (month < 1) - { - this.month = 1; - } - else - { - if (month > 12) - { - this.month = 12; - } - else - { - this.month = month; - } - } - this.hasDate = true; - } - - /// - public virtual int GetDay() - { - return day; - } - - /// - public virtual void SetDay(int day) - { - if (day < 1) - { - this.day = 1; - } - else - { - if (day > 31) - { - this.day = 31; - } - else - { - this.day = day; - } - } - this.hasDate = true; - } - - /// - public virtual int GetHour() - { - return hour; - } - - /// - public virtual void SetHour(int hour) - { - this.hour = Math.Min(Math.Abs(hour), 23); - this.hasTime = true; - } - - /// - public virtual int GetMinute() - { - return minute; - } - - /// - public virtual void SetMinute(int minute) - { - this.minute = Math.Min(Math.Abs(minute), 59); - this.hasTime = true; - } - - /// - public virtual int GetSecond() - { - return second; - } - - /// - public virtual void SetSecond(int second) - { - this.second = Math.Min(Math.Abs(second), 59); - this.hasTime = true; - } - - /// - public virtual int GetNanoSecond() - { - return nanoSeconds; - } - - /// - public virtual void SetNanoSecond(int nanoSecond) - { - this.nanoSeconds = nanoSecond; - this.hasTime = true; - } - - /// - public virtual int CompareTo(object dt) - { - long d = GetCalendar().GetTimeInMillis() - ((XMPDateTime)dt).GetCalendar().GetTimeInMillis(); - if (d != 0) - { - return (int)Math.Sign(d); - } - else - { - // if millis are equal, compare nanoseconds - d = nanoSeconds - ((XMPDateTime)dt).GetNanoSecond(); - return (int)Math.Sign(d); - } - } - - /// - public virtual TimeZoneInfo GetTimeZone() - { - return timeZone; - } - - /// - public virtual void SetTimeZone(TimeZoneInfo timeZone) - { - this.timeZone = timeZone; - this.hasTime = true; - this.hasTimeZone = true; - } - - /// - public virtual bool HasDate() - { - return this.hasDate; - } - - /// - public virtual bool HasTime() - { - return this.hasTime; - } - - /// - public virtual bool HasTimeZone() - { - return this.hasTimeZone; - } - - /// - public virtual Calendar GetCalendar() - { - GregorianCalendar calendar = (GregorianCalendar)Calendar.GetInstance(CultureInfo.InvariantCulture); - calendar.SetGregorianChange(Extensions.CreateDate(long.MinValue)); - if (hasTimeZone) - { - calendar.SetTimeZone(timeZone); - } - calendar.Set(CalendarEnum.Year, year); - calendar.Set(CalendarEnum.Month, month - 1); - calendar.Set(CalendarEnum.DayOfMonth, day); - calendar.Set(CalendarEnum.HourOfDay, hour); - calendar.Set(CalendarEnum.Minute, minute); - calendar.Set(CalendarEnum.Second, second); - calendar.Set(CalendarEnum.Millisecond, nanoSeconds / 1000000); - return calendar; - } - - /// - public virtual string GetISO8601String() - { - return ISO8601Converter.Render(this); - } - - /// Returns the ISO string representation. - public override string ToString() - { - return GetISO8601String(); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using System.Globalization; +using Sharpen; +using Calendar = Sharpen.Calendar; +using GregorianCalendar = Sharpen.GregorianCalendar; + +namespace Com.Adobe.Xmp.Impl +{ + /// The implementation of XMPDateTime. + /// + /// The implementation of XMPDateTime. Internally a calendar is used + /// plus an additional nano seconds field, because Calendar supports only milli + /// seconds. The nanoSeconds convers only the resolution beyond a milli second. + /// + /// 16.02.2006 + public class XMPDateTimeImpl : XMPDateTime + { + private int year = 0; + + private int month = 0; + + private int day = 0; + + private int hour = 0; + + private int minute = 0; + + private int second = 0; + + /// Use NO time zone as default + private TimeZoneInfo timeZone = null; + + /// The nano seconds take micro and nano seconds, while the milli seconds are in the calendar. + private int nanoSeconds; + + private bool hasDate = false; + + private bool hasTime = false; + + private bool hasTimeZone = false; + + /// + /// Creates an XMPDateTime-instance with the current time in the default time + /// zone. + /// + public XMPDateTimeImpl() + { + } + + /// Creates an XMPDateTime-instance from a calendar. + /// a Calendar + public XMPDateTimeImpl(Calendar calendar) + { + // EMPTY + // extract the date and timezone from the calendar provided + DateTime date = calendar.GetTime(); + TimeZoneInfo zone = calendar.GetTimeZone(); + // put that date into a calendar the pretty much represents ISO8601 + // I use US because it is close to the "locale" for the ISO8601 spec + GregorianCalendar intCalendar = (GregorianCalendar)Calendar.GetInstance(CultureInfo.InvariantCulture); + intCalendar.SetGregorianChange(Extensions.CreateDate(long.MinValue)); + intCalendar.SetTimeZone(zone); + intCalendar.SetTime(date); + this.year = intCalendar.Get(CalendarEnum.Year); + this.month = intCalendar.Get(CalendarEnum.Month) + 1; + // cal is from 0..12 + this.day = intCalendar.Get(CalendarEnum.DayOfMonth); + this.hour = intCalendar.Get(CalendarEnum.HourOfDay); + this.minute = intCalendar.Get(CalendarEnum.Minute); + this.second = intCalendar.Get(CalendarEnum.Second); + this.nanoSeconds = intCalendar.Get(CalendarEnum.Millisecond) * 1000000; + this.timeZone = intCalendar.GetTimeZone(); + // object contains all date components + hasDate = hasTime = hasTimeZone = true; + } + + /// + /// Creates an XMPDateTime-instance from + /// a Date and a TimeZone. + /// + /// a date describing an absolute point in time + /// a TimeZone how to interpret the date + public XMPDateTimeImpl(DateTime date, TimeZoneInfo timeZone) + { + GregorianCalendar calendar = new GregorianCalendar(timeZone); + calendar.SetTime(date); + this.year = calendar.Get(CalendarEnum.Year); + this.month = calendar.Get(CalendarEnum.Month) + 1; + // cal is from 0..12 + this.day = calendar.Get(CalendarEnum.DayOfMonth); + this.hour = calendar.Get(CalendarEnum.HourOfDay); + this.minute = calendar.Get(CalendarEnum.Minute); + this.second = calendar.Get(CalendarEnum.Second); + this.nanoSeconds = calendar.Get(CalendarEnum.Millisecond) * 1000000; + this.timeZone = timeZone; + // object contains all date components + hasDate = hasTime = hasTimeZone = true; + } + + /// Creates an XMPDateTime-instance from an ISO 8601 string. + /// an ISO 8601 string + /// If the string is a non-conform ISO 8601 string, an exception is thrown + public XMPDateTimeImpl(string strValue) + { + ISO8601Converter.Parse(strValue, this); + } + + /// + public virtual int GetYear() + { + return year; + } + + /// + public virtual void SetYear(int year) + { + this.year = Math.Min(Math.Abs(year), 9999); + this.hasDate = true; + } + + /// + public virtual int GetMonth() + { + return month; + } + + /// + public virtual void SetMonth(int month) + { + if (month < 1) + { + this.month = 1; + } + else + { + if (month > 12) + { + this.month = 12; + } + else + { + this.month = month; + } + } + this.hasDate = true; + } + + /// + public virtual int GetDay() + { + return day; + } + + /// + public virtual void SetDay(int day) + { + if (day < 1) + { + this.day = 1; + } + else + { + if (day > 31) + { + this.day = 31; + } + else + { + this.day = day; + } + } + this.hasDate = true; + } + + /// + public virtual int GetHour() + { + return hour; + } + + /// + public virtual void SetHour(int hour) + { + this.hour = Math.Min(Math.Abs(hour), 23); + this.hasTime = true; + } + + /// + public virtual int GetMinute() + { + return minute; + } + + /// + public virtual void SetMinute(int minute) + { + this.minute = Math.Min(Math.Abs(minute), 59); + this.hasTime = true; + } + + /// + public virtual int GetSecond() + { + return second; + } + + /// + public virtual void SetSecond(int second) + { + this.second = Math.Min(Math.Abs(second), 59); + this.hasTime = true; + } + + /// + public virtual int GetNanoSecond() + { + return nanoSeconds; + } + + /// + public virtual void SetNanoSecond(int nanoSecond) + { + this.nanoSeconds = nanoSecond; + this.hasTime = true; + } + + /// + public virtual int CompareTo(object dt) + { + long d = GetCalendar().GetTimeInMillis() - ((XMPDateTime)dt).GetCalendar().GetTimeInMillis(); + if (d != 0) + { + return (int)Math.Sign(d); + } + else + { + // if millis are equal, compare nanoseconds + d = nanoSeconds - ((XMPDateTime)dt).GetNanoSecond(); + return (int)Math.Sign(d); + } + } + + /// + public virtual TimeZoneInfo GetTimeZone() + { + return timeZone; + } + + /// + public virtual void SetTimeZone(TimeZoneInfo timeZone) + { + this.timeZone = timeZone; + this.hasTime = true; + this.hasTimeZone = true; + } + + /// + public virtual bool HasDate() + { + return this.hasDate; + } + + /// + public virtual bool HasTime() + { + return this.hasTime; + } + + /// + public virtual bool HasTimeZone() + { + return this.hasTimeZone; + } + + /// + public virtual Calendar GetCalendar() + { + GregorianCalendar calendar = (GregorianCalendar)Calendar.GetInstance(CultureInfo.InvariantCulture); + calendar.SetGregorianChange(Extensions.CreateDate(long.MinValue)); + if (hasTimeZone) + { + calendar.SetTimeZone(timeZone); + } + calendar.Set(CalendarEnum.Year, year); + calendar.Set(CalendarEnum.Month, month - 1); + calendar.Set(CalendarEnum.DayOfMonth, day); + calendar.Set(CalendarEnum.HourOfDay, hour); + calendar.Set(CalendarEnum.Minute, minute); + calendar.Set(CalendarEnum.Second, second); + calendar.Set(CalendarEnum.Millisecond, nanoSeconds / 1000000); + return calendar; + } + + /// + public virtual string GetISO8601String() + { + return ISO8601Converter.Render(this); + } + + /// Returns the ISO string representation. + public override string ToString() + { + return GetISO8601String(); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPIteratorImpl.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPIteratorImpl.cs index 32529ef76..e5f8a5f26 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPIteratorImpl.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPIteratorImpl.cs @@ -1,568 +1,568 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using Com.Adobe.Xmp.Impl.Xpath; -using Com.Adobe.Xmp.Options; -using Com.Adobe.Xmp.Properties; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// The XMPIterator implementation. - /// - /// The XMPIterator implementation. - /// Iterates the XMP Tree according to a set of options. - /// During the iteration the XMPMeta-object must not be changed. - /// Calls to skipSubtree() / skipSiblings() will affect the iteration. - /// - /// 29.06.2006 - public class XMPIteratorImpl : XMPIterator - { - /// stores the iterator options - private IteratorOptions options; - - /// the base namespace of the property path, will be changed during the iteration - private string baseNS = null; - - /// flag to indicate that skipSiblings() has been called. - protected internal bool skipSiblings = false; - - /// flag to indicate that skipSiblings() has been called. - protected internal bool skipSubtree = false; - - /// the node iterator doing the work - private Iterator nodeIterator = null; - - /// Constructor with optionsl initial values. - /// - /// Constructor with optionsl initial values. If propName is provided, - /// schemaNS has also be provided. - /// - /// the iterated metadata object. - /// the iteration is reduced to this schema (optional) - /// the iteration is redurce to this property within the schemaNS - /// - /// advanced iteration options, see - /// - /// - /// If the node defined by the paramters is not existing. - public XMPIteratorImpl(XMPMetaImpl xmp, string schemaNS, string propPath, IteratorOptions options) - { - // make sure that options is defined at least with defaults - this.options = options != null ? options : new IteratorOptions(); - // the start node of the iteration depending on the schema and property filter - XMPNode startNode = null; - string initialPath = null; - bool baseSchema = schemaNS != null && schemaNS.Length > 0; - bool baseProperty = propPath != null && propPath.Length > 0; - if (!baseSchema && !baseProperty) - { - // complete tree will be iterated - startNode = xmp.GetRoot(); - } - else - { - if (baseSchema && baseProperty) - { - // Schema and property node provided - XMPPath path = XMPPathParser.ExpandXPath(schemaNS, propPath); - // base path is the prop path without the property leaf - XMPPath basePath = new XMPPath(); - for (int i = 0; i < path.Size() - 1; i++) - { - basePath.Add(path.GetSegment(i)); - } - startNode = XMPNodeUtils.FindNode(xmp.GetRoot(), path, false, null); - baseNS = schemaNS; - initialPath = basePath.ToString(); - } - else - { - if (baseSchema && !baseProperty) - { - // Only Schema provided - startNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNS, false); - } - else - { - // !baseSchema && baseProperty - // No schema but property provided -> error - throw new XMPException("Schema namespace URI is required", XMPErrorConstants.Badschema); - } - } - } - // create iterator - if (startNode != null) - { - if (!this.options.IsJustChildren()) - { - nodeIterator = new NodeIterator(this, startNode, initialPath, 1); - } - else - { - nodeIterator = new NodeIteratorChildren(this, startNode, initialPath); - } - } - else - { - // create null iterator - nodeIterator = Collections.EmptyList().Iterator(); - } - } - - /// - public virtual void SkipSubtree() - { - this.skipSubtree = true; - } - - /// - public virtual void SkipSiblings() - { - SkipSubtree(); - this.skipSiblings = true; - } - - /// - public virtual bool HasNext() - { - return nodeIterator.HasNext(); - } - - /// - public virtual object Next() - { - return nodeIterator.Next(); - } - - /// - public virtual void Remove() - { - throw new NotSupportedException("The XMPIterator does not support remove()."); - } - - /// Exposes the options for inner class. - protected internal virtual IteratorOptions GetOptions() - { - return options; - } - - /// Exposes the options for inner class. - protected internal virtual string GetBaseNS() - { - return baseNS; - } - - /// sets the baseNS from the inner class. - protected internal virtual void SetBaseNS(string baseNS) - { - this.baseNS = baseNS; - } - - /// The XMPIterator implementation. - /// - /// The XMPIterator implementation. - /// It first returns the node itself, then recursivly the children and qualifier of the node. - /// - /// 29.06.2006 - private class NodeIterator : Iterator - { - /// iteration state - protected internal const int IterateNode = 0; - - /// iteration state - protected internal const int IterateChildren = 1; - - /// iteration state - protected internal const int IterateQualifier = 2; - - /// the state of the iteration - private int state = IterateNode; - - /// the currently visited node - private XMPNode visitedNode; - - /// the recursively accumulated path - private string path; - - /// the iterator that goes through the children and qualifier list - private Iterator childrenIterator = null; - - /// index of node with parent, only interesting for arrays - private int index = 0; - - /// the iterator for each child - private Iterator subIterator = Collections.EmptyList().Iterator(); - - /// the cached PropertyInfo to return - private XMPPropertyInfo returnProperty = null; - - /// Default constructor - public NodeIterator(XMPIteratorImpl _enclosing) - { - this._enclosing = _enclosing; - } - - /// Constructor for the node iterator. - /// the currently visited node - /// the accumulated path of the node - /// the index within the parent node (only for arrays) - public NodeIterator(XMPIteratorImpl _enclosing, XMPNode visitedNode, string parentPath, int index) - { - this._enclosing = _enclosing; - // EMPTY - this.visitedNode = visitedNode; - this.state = IterateNode; - if (visitedNode.GetOptions().IsSchemaNode()) - { - this._enclosing.SetBaseNS(visitedNode.GetName()); - } - // for all but the root node and schema nodes - this.path = this.AccumulatePath(visitedNode, parentPath, index); - } - - /// Prepares the next node to return if not already done. - /// - public virtual bool HasNext() - { - if (this.returnProperty != null) - { - // hasNext has been called before - return true; - } - // find next node - if (this.state == IterateNode) - { - return this.ReportNode(); - } - else - { - if (this.state == IterateChildren) - { - if (this.childrenIterator == null) - { - this.childrenIterator = this.visitedNode.IterateChildren(); - } - bool hasNext = this.IterateChildrenMethod(this.childrenIterator); - if (!hasNext && this.visitedNode.HasQualifier() && !this._enclosing.GetOptions().IsOmitQualifiers()) - { - this.state = IterateQualifier; - this.childrenIterator = null; - hasNext = this.HasNext(); - } - return hasNext; - } - else - { - if (this.childrenIterator == null) - { - this.childrenIterator = this.visitedNode.IterateQualifier(); - } - return this.IterateChildrenMethod(this.childrenIterator); - } - } - } - - /// Sets the returnProperty as next item or recurses into hasNext(). - /// Returns if there is a next item to return. - protected internal virtual bool ReportNode() - { - this.state = IterateChildren; - if (this.visitedNode.GetParent() != null && (!this._enclosing.GetOptions().IsJustLeafnodes() || !this.visitedNode.HasChildren())) - { - this.returnProperty = this.CreatePropertyInfo(this.visitedNode, this._enclosing.GetBaseNS(), this.path); - return true; - } - else - { - return this.HasNext(); - } - } - - /// Handles the iteration of the children or qualfier - /// an iterator - /// Returns if there are more elements available. - private bool IterateChildrenMethod(Iterator iterator) - { - if (this._enclosing.skipSiblings) - { - // setSkipSiblings(false); - this._enclosing.skipSiblings = false; - this.subIterator = Collections.EmptyList().Iterator(); - } - // create sub iterator for every child, - // if its the first child visited or the former child is finished - if ((!this.subIterator.HasNext()) && iterator.HasNext()) - { - XMPNode child = (XMPNode)iterator.Next(); - this.index++; - this.subIterator = new NodeIterator(this._enclosing, child, this.path, this.index); - } - if (this.subIterator.HasNext()) - { - this.returnProperty = (XMPPropertyInfo)this.subIterator.Next(); - return true; - } - else - { - return false; - } - } - - /// Calls hasNext() and returnes the prepared node. - /// - /// Calls hasNext() and returnes the prepared node. Afterwards its set to null. - /// The existance of returnProperty indicates if there is a next node, otherwise - /// an exceptio is thrown. - /// - /// - public virtual object Next() - { - if (this.HasNext()) - { - XMPPropertyInfo result = this.returnProperty; - this.returnProperty = null; - return result; - } - else - { - throw new NoSuchElementException("There are no more nodes to return"); - } - } - - /// Not supported. - /// - public virtual void Remove() - { - throw new NotSupportedException(); - } - - /// the node that will be added to the path. - /// the path up to this node. - /// the current array index if an arrey is traversed - /// Returns the updated path. - protected internal virtual string AccumulatePath(XMPNode currNode, string parentPath, int currentIndex) - { - string separator; - string segmentName; - if (currNode.GetParent() == null || currNode.GetOptions().IsSchemaNode()) - { - return null; - } - else - { - if (currNode.GetParent().GetOptions().IsArray()) - { - separator = string.Empty; - segmentName = "[" + currentIndex.ToString() + "]"; - } - else - { - separator = "/"; - segmentName = currNode.GetName(); - } - } - if (parentPath == null || parentPath.Length == 0) - { - return segmentName; - } - else - { - if (this._enclosing.GetOptions().IsJustLeafname()) - { - return !segmentName.StartsWith("?") ? segmentName : Runtime.Substring(segmentName, 1); - } - else - { - // qualifier - return parentPath + separator + segmentName; - } - } - } - - /// Creates a property info object from an XMPNode. - /// an XMPNode - /// the base namespace to report - /// the full property path - /// Returns a XMPProperty-object that serves representation of the node. - protected internal virtual XMPPropertyInfo CreatePropertyInfo(XMPNode node, string baseNS, string path) - { - string value = node.GetOptions().IsSchemaNode() ? null : node.GetValue(); - return new _XMPPropertyInfo_450(node, baseNS, path, value); - } - - private sealed class _XMPPropertyInfo_450 : XMPPropertyInfo - { - public _XMPPropertyInfo_450(XMPNode node, string baseNS, string path, string value) - { - this.node = node; - this.baseNS = baseNS; - this.path = path; - this.value = value; - } - - public string GetNamespace() - { - if (!node.GetOptions().IsSchemaNode()) - { - // determine namespace of leaf node - QName qname = new QName(node.GetName()); - return XMPMetaFactory.GetSchemaRegistry().GetNamespaceURI(qname.GetPrefix()); - } - else - { - return baseNS; - } - } - - public string GetPath() - { - return path; - } - - public string GetValue() - { - return value; - } - - public PropertyOptions GetOptions() - { - return node.GetOptions(); - } - - public string GetLanguage() - { - // the language is not reported - return null; - } - - private readonly XMPNode node; - - private readonly string baseNS; - - private readonly string path; - - private readonly string value; - } - - /// the childrenIterator - protected internal virtual Iterator GetChildrenIterator() - { - return this.childrenIterator; - } - - /// the childrenIterator to set - protected internal virtual void SetChildrenIterator(Iterator childrenIterator) - { - this.childrenIterator = childrenIterator; - } - - /// Returns the returnProperty. - protected internal virtual XMPPropertyInfo GetReturnProperty() - { - return this.returnProperty; - } - - /// the returnProperty to set - protected internal virtual void SetReturnProperty(XMPPropertyInfo returnProperty) - { - this.returnProperty = returnProperty; - } - - private readonly XMPIteratorImpl _enclosing; - } - - /// - /// This iterator is derived from the default NodeIterator, - /// and is only used for the option - /// - /// . - /// - /// 02.10.2006 - private class NodeIteratorChildren : NodeIterator - { - private string parentPath; - - private Iterator childrenIterator; - - private int index = 0; - - /// Constructor - /// the node which children shall be iterated. - /// the full path of the former node without the leaf node. - public NodeIteratorChildren(XMPIteratorImpl _enclosing, XMPNode parentNode, string parentPath) - : base(_enclosing) - { - this._enclosing = _enclosing; - if (parentNode.GetOptions().IsSchemaNode()) - { - this._enclosing.SetBaseNS(parentNode.GetName()); - } - this.parentPath = this.AccumulatePath(parentNode, parentPath, 1); - this.childrenIterator = parentNode.IterateChildren(); - } - - /// Prepares the next node to return if not already done. - /// - public override bool HasNext() - { - if (this.GetReturnProperty() != null) - { - // hasNext has been called before - return true; - } - else - { - if (this._enclosing.skipSiblings) - { - return false; - } - else - { - if (this.childrenIterator.HasNext()) - { - XMPNode child = (XMPNode)this.childrenIterator.Next(); - this.index++; - string path = null; - if (child.GetOptions().IsSchemaNode()) - { - this._enclosing.SetBaseNS(child.GetName()); - } - else - { - if (child.GetParent() != null) - { - // for all but the root node and schema nodes - path = this.AccumulatePath(child, this.parentPath, this.index); - } - } - // report next property, skip not-leaf nodes in case options is set - if (!this._enclosing.GetOptions().IsJustLeafnodes() || !child.HasChildren()) - { - this.SetReturnProperty(this.CreatePropertyInfo(child, this._enclosing.GetBaseNS(), path)); - return true; - } - else - { - return this.HasNext(); - } - } - else - { - return false; - } - } - } - } - - private readonly XMPIteratorImpl _enclosing; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using Com.Adobe.Xmp.Impl.Xpath; +using Com.Adobe.Xmp.Options; +using Com.Adobe.Xmp.Properties; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// The XMPIterator implementation. + /// + /// The XMPIterator implementation. + /// Iterates the XMP Tree according to a set of options. + /// During the iteration the XMPMeta-object must not be changed. + /// Calls to skipSubtree() / skipSiblings() will affect the iteration. + /// + /// 29.06.2006 + public class XMPIteratorImpl : XMPIterator + { + /// stores the iterator options + private IteratorOptions options; + + /// the base namespace of the property path, will be changed during the iteration + private string baseNS = null; + + /// flag to indicate that skipSiblings() has been called. + protected internal bool skipSiblings = false; + + /// flag to indicate that skipSiblings() has been called. + protected internal bool skipSubtree = false; + + /// the node iterator doing the work + private Iterator nodeIterator = null; + + /// Constructor with optionsl initial values. + /// + /// Constructor with optionsl initial values. If propName is provided, + /// schemaNS has also be provided. + /// + /// the iterated metadata object. + /// the iteration is reduced to this schema (optional) + /// the iteration is redurce to this property within the schemaNS + /// + /// advanced iteration options, see + /// + /// + /// If the node defined by the paramters is not existing. + public XMPIteratorImpl(XMPMetaImpl xmp, string schemaNS, string propPath, IteratorOptions options) + { + // make sure that options is defined at least with defaults + this.options = options != null ? options : new IteratorOptions(); + // the start node of the iteration depending on the schema and property filter + XMPNode startNode = null; + string initialPath = null; + bool baseSchema = schemaNS != null && schemaNS.Length > 0; + bool baseProperty = propPath != null && propPath.Length > 0; + if (!baseSchema && !baseProperty) + { + // complete tree will be iterated + startNode = xmp.GetRoot(); + } + else + { + if (baseSchema && baseProperty) + { + // Schema and property node provided + XMPPath path = XMPPathParser.ExpandXPath(schemaNS, propPath); + // base path is the prop path without the property leaf + XMPPath basePath = new XMPPath(); + for (int i = 0; i < path.Size() - 1; i++) + { + basePath.Add(path.GetSegment(i)); + } + startNode = XMPNodeUtils.FindNode(xmp.GetRoot(), path, false, null); + baseNS = schemaNS; + initialPath = basePath.ToString(); + } + else + { + if (baseSchema && !baseProperty) + { + // Only Schema provided + startNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNS, false); + } + else + { + // !baseSchema && baseProperty + // No schema but property provided -> error + throw new XMPException("Schema namespace URI is required", XMPErrorConstants.Badschema); + } + } + } + // create iterator + if (startNode != null) + { + if (!this.options.IsJustChildren()) + { + nodeIterator = new NodeIterator(this, startNode, initialPath, 1); + } + else + { + nodeIterator = new NodeIteratorChildren(this, startNode, initialPath); + } + } + else + { + // create null iterator + nodeIterator = Collections.EmptyList().Iterator(); + } + } + + /// + public virtual void SkipSubtree() + { + this.skipSubtree = true; + } + + /// + public virtual void SkipSiblings() + { + SkipSubtree(); + this.skipSiblings = true; + } + + /// + public virtual bool HasNext() + { + return nodeIterator.HasNext(); + } + + /// + public virtual object Next() + { + return nodeIterator.Next(); + } + + /// + public virtual void Remove() + { + throw new NotSupportedException("The XMPIterator does not support remove()."); + } + + /// Exposes the options for inner class. + protected internal virtual IteratorOptions GetOptions() + { + return options; + } + + /// Exposes the options for inner class. + protected internal virtual string GetBaseNS() + { + return baseNS; + } + + /// sets the baseNS from the inner class. + protected internal virtual void SetBaseNS(string baseNS) + { + this.baseNS = baseNS; + } + + /// The XMPIterator implementation. + /// + /// The XMPIterator implementation. + /// It first returns the node itself, then recursivly the children and qualifier of the node. + /// + /// 29.06.2006 + private class NodeIterator : Iterator + { + /// iteration state + protected internal const int IterateNode = 0; + + /// iteration state + protected internal const int IterateChildren = 1; + + /// iteration state + protected internal const int IterateQualifier = 2; + + /// the state of the iteration + private int state = IterateNode; + + /// the currently visited node + private XMPNode visitedNode; + + /// the recursively accumulated path + private string path; + + /// the iterator that goes through the children and qualifier list + private Iterator childrenIterator = null; + + /// index of node with parent, only interesting for arrays + private int index = 0; + + /// the iterator for each child + private Iterator subIterator = Collections.EmptyList().Iterator(); + + /// the cached PropertyInfo to return + private XMPPropertyInfo returnProperty = null; + + /// Default constructor + public NodeIterator(XMPIteratorImpl _enclosing) + { + this._enclosing = _enclosing; + } + + /// Constructor for the node iterator. + /// the currently visited node + /// the accumulated path of the node + /// the index within the parent node (only for arrays) + public NodeIterator(XMPIteratorImpl _enclosing, XMPNode visitedNode, string parentPath, int index) + { + this._enclosing = _enclosing; + // EMPTY + this.visitedNode = visitedNode; + this.state = IterateNode; + if (visitedNode.GetOptions().IsSchemaNode()) + { + this._enclosing.SetBaseNS(visitedNode.GetName()); + } + // for all but the root node and schema nodes + this.path = this.AccumulatePath(visitedNode, parentPath, index); + } + + /// Prepares the next node to return if not already done. + /// + public virtual bool HasNext() + { + if (this.returnProperty != null) + { + // hasNext has been called before + return true; + } + // find next node + if (this.state == IterateNode) + { + return this.ReportNode(); + } + else + { + if (this.state == IterateChildren) + { + if (this.childrenIterator == null) + { + this.childrenIterator = this.visitedNode.IterateChildren(); + } + bool hasNext = this.IterateChildrenMethod(this.childrenIterator); + if (!hasNext && this.visitedNode.HasQualifier() && !this._enclosing.GetOptions().IsOmitQualifiers()) + { + this.state = IterateQualifier; + this.childrenIterator = null; + hasNext = this.HasNext(); + } + return hasNext; + } + else + { + if (this.childrenIterator == null) + { + this.childrenIterator = this.visitedNode.IterateQualifier(); + } + return this.IterateChildrenMethod(this.childrenIterator); + } + } + } + + /// Sets the returnProperty as next item or recurses into hasNext(). + /// Returns if there is a next item to return. + protected internal virtual bool ReportNode() + { + this.state = IterateChildren; + if (this.visitedNode.GetParent() != null && (!this._enclosing.GetOptions().IsJustLeafnodes() || !this.visitedNode.HasChildren())) + { + this.returnProperty = this.CreatePropertyInfo(this.visitedNode, this._enclosing.GetBaseNS(), this.path); + return true; + } + else + { + return this.HasNext(); + } + } + + /// Handles the iteration of the children or qualfier + /// an iterator + /// Returns if there are more elements available. + private bool IterateChildrenMethod(Iterator iterator) + { + if (this._enclosing.skipSiblings) + { + // setSkipSiblings(false); + this._enclosing.skipSiblings = false; + this.subIterator = Collections.EmptyList().Iterator(); + } + // create sub iterator for every child, + // if its the first child visited or the former child is finished + if ((!this.subIterator.HasNext()) && iterator.HasNext()) + { + XMPNode child = (XMPNode)iterator.Next(); + this.index++; + this.subIterator = new NodeIterator(this._enclosing, child, this.path, this.index); + } + if (this.subIterator.HasNext()) + { + this.returnProperty = (XMPPropertyInfo)this.subIterator.Next(); + return true; + } + else + { + return false; + } + } + + /// Calls hasNext() and returnes the prepared node. + /// + /// Calls hasNext() and returnes the prepared node. Afterwards its set to null. + /// The existance of returnProperty indicates if there is a next node, otherwise + /// an exceptio is thrown. + /// + /// + public virtual object Next() + { + if (this.HasNext()) + { + XMPPropertyInfo result = this.returnProperty; + this.returnProperty = null; + return result; + } + else + { + throw new NoSuchElementException("There are no more nodes to return"); + } + } + + /// Not supported. + /// + public virtual void Remove() + { + throw new NotSupportedException(); + } + + /// the node that will be added to the path. + /// the path up to this node. + /// the current array index if an arrey is traversed + /// Returns the updated path. + protected internal virtual string AccumulatePath(XMPNode currNode, string parentPath, int currentIndex) + { + string separator; + string segmentName; + if (currNode.GetParent() == null || currNode.GetOptions().IsSchemaNode()) + { + return null; + } + else + { + if (currNode.GetParent().GetOptions().IsArray()) + { + separator = string.Empty; + segmentName = "[" + currentIndex.ToString() + "]"; + } + else + { + separator = "/"; + segmentName = currNode.GetName(); + } + } + if (parentPath == null || parentPath.Length == 0) + { + return segmentName; + } + else + { + if (this._enclosing.GetOptions().IsJustLeafname()) + { + return !segmentName.StartsWith("?") ? segmentName : Runtime.Substring(segmentName, 1); + } + else + { + // qualifier + return parentPath + separator + segmentName; + } + } + } + + /// Creates a property info object from an XMPNode. + /// an XMPNode + /// the base namespace to report + /// the full property path + /// Returns a XMPProperty-object that serves representation of the node. + protected internal virtual XMPPropertyInfo CreatePropertyInfo(XMPNode node, string baseNS, string path) + { + string value = node.GetOptions().IsSchemaNode() ? null : node.GetValue(); + return new _XMPPropertyInfo_450(node, baseNS, path, value); + } + + private sealed class _XMPPropertyInfo_450 : XMPPropertyInfo + { + public _XMPPropertyInfo_450(XMPNode node, string baseNS, string path, string value) + { + this.node = node; + this.baseNS = baseNS; + this.path = path; + this.value = value; + } + + public string GetNamespace() + { + if (!node.GetOptions().IsSchemaNode()) + { + // determine namespace of leaf node + QName qname = new QName(node.GetName()); + return XMPMetaFactory.GetSchemaRegistry().GetNamespaceURI(qname.GetPrefix()); + } + else + { + return baseNS; + } + } + + public string GetPath() + { + return path; + } + + public string GetValue() + { + return value; + } + + public PropertyOptions GetOptions() + { + return node.GetOptions(); + } + + public string GetLanguage() + { + // the language is not reported + return null; + } + + private readonly XMPNode node; + + private readonly string baseNS; + + private readonly string path; + + private readonly string value; + } + + /// the childrenIterator + protected internal virtual Iterator GetChildrenIterator() + { + return this.childrenIterator; + } + + /// the childrenIterator to set + protected internal virtual void SetChildrenIterator(Iterator childrenIterator) + { + this.childrenIterator = childrenIterator; + } + + /// Returns the returnProperty. + protected internal virtual XMPPropertyInfo GetReturnProperty() + { + return this.returnProperty; + } + + /// the returnProperty to set + protected internal virtual void SetReturnProperty(XMPPropertyInfo returnProperty) + { + this.returnProperty = returnProperty; + } + + private readonly XMPIteratorImpl _enclosing; + } + + /// + /// This iterator is derived from the default NodeIterator, + /// and is only used for the option + /// + /// . + /// + /// 02.10.2006 + private class NodeIteratorChildren : NodeIterator + { + private string parentPath; + + private Iterator childrenIterator; + + private int index = 0; + + /// Constructor + /// the node which children shall be iterated. + /// the full path of the former node without the leaf node. + public NodeIteratorChildren(XMPIteratorImpl _enclosing, XMPNode parentNode, string parentPath) + : base(_enclosing) + { + this._enclosing = _enclosing; + if (parentNode.GetOptions().IsSchemaNode()) + { + this._enclosing.SetBaseNS(parentNode.GetName()); + } + this.parentPath = this.AccumulatePath(parentNode, parentPath, 1); + this.childrenIterator = parentNode.IterateChildren(); + } + + /// Prepares the next node to return if not already done. + /// + public override bool HasNext() + { + if (this.GetReturnProperty() != null) + { + // hasNext has been called before + return true; + } + else + { + if (this._enclosing.skipSiblings) + { + return false; + } + else + { + if (this.childrenIterator.HasNext()) + { + XMPNode child = (XMPNode)this.childrenIterator.Next(); + this.index++; + string path = null; + if (child.GetOptions().IsSchemaNode()) + { + this._enclosing.SetBaseNS(child.GetName()); + } + else + { + if (child.GetParent() != null) + { + // for all but the root node and schema nodes + path = this.AccumulatePath(child, this.parentPath, this.index); + } + } + // report next property, skip not-leaf nodes in case options is set + if (!this._enclosing.GetOptions().IsJustLeafnodes() || !child.HasChildren()) + { + this.SetReturnProperty(this.CreatePropertyInfo(child, this._enclosing.GetBaseNS(), path)); + return true; + } + else + { + return this.HasNext(); + } + } + else + { + return false; + } + } + } + } + + private readonly XMPIteratorImpl _enclosing; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaImpl.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaImpl.cs index 88fe1414c..81a5d1475 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaImpl.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaImpl.cs @@ -1,1176 +1,1176 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Diagnostics; -using Com.Adobe.Xmp.Impl.Xpath; -using Com.Adobe.Xmp.Options; -using Com.Adobe.Xmp.Properties; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// - /// Implementation for - /// - /// . - /// - /// 17.02.2006 - public class XMPMetaImpl : XMPMeta, XMPConst - { - /// Property values are Strings by default - private const int ValueString = 0; - - private const int ValueBoolean = 1; - - private const int ValueInteger = 2; - - private const int ValueLong = 3; - - private const int ValueDouble = 4; - - private const int ValueDate = 5; - - private const int ValueCalendar = 6; - - private const int ValueBase64 = 7; - - /// root of the metadata tree - private XMPNode tree; - - /// the xpacket processing instructions content - private string packetHeader = null; - - /// Constructor for an empty metadata object. - public XMPMetaImpl() - { - // create root node - tree = new XMPNode(null, null, null); - } - - /// Constructor for a cloned metadata tree. - /// - /// an prefilled metadata tree which fulfills all - /// XMPNode contracts. - /// - public XMPMetaImpl(XMPNode tree) - { - this.tree = tree; - } - - /// - /// - public virtual void AppendArrayItem(string schemaNS, string arrayName, PropertyOptions arrayOptions, string itemValue, PropertyOptions itemOptions) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - if (arrayOptions == null) - { - arrayOptions = new PropertyOptions(); - } - if (!arrayOptions.IsOnlyArrayOptions()) - { - throw new XMPException("Only array form flags allowed for arrayOptions", XMPErrorConstants.Badoptions); - } - // Check if array options are set correctly. - arrayOptions = XMPNodeUtils.VerifySetOptions(arrayOptions, null); - // Locate or create the array. If it already exists, make sure the array - // form from the options - // parameter is compatible with the current state. - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); - // Just lookup, don't try to create. - XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); - if (arrayNode != null) - { - // The array exists, make sure the form is compatible. Zero - // arrayForm means take what exists. - if (!arrayNode.GetOptions().IsArray()) - { - throw new XMPException("The named property is not an array", XMPErrorConstants.Badxpath); - } - } - else - { - // if (arrayOptions != null && !arrayOptions.equalArrayTypes(arrayNode.getOptions())) - // { - // throw new XMPException("Mismatch of existing and specified array form", BADOPTIONS); - // } - // The array does not exist, try to create it. - if (arrayOptions.IsArray()) - { - arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, true, arrayOptions); - if (arrayNode == null) - { - throw new XMPException("Failure creating array node", XMPErrorConstants.Badxpath); - } - } - else - { - // array options missing - throw new XMPException("Explicit arrayOptions required to create new array", XMPErrorConstants.Badoptions); - } - } - DoSetArrayItem(arrayNode, XMPConstConstants.ArrayLastItem, itemValue, itemOptions, true); - } - - /// - /// - public virtual void AppendArrayItem(string schemaNS, string arrayName, string itemValue) - { - AppendArrayItem(schemaNS, arrayName, null, itemValue, null); - } - - /// - /// - public virtual int CountArrayItems(string schemaNS, string arrayName) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); - XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); - if (arrayNode == null) - { - return 0; - } - if (arrayNode.GetOptions().IsArray()) - { - return arrayNode.GetChildrenLength(); - } - else - { - throw new XMPException("The named property is not an array", XMPErrorConstants.Badxpath); - } - } - - /// - public virtual void DeleteArrayItem(string schemaNS, string arrayName, int itemIndex) - { - try - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - string itemPath = XMPPathFactory.ComposeArrayItemPath(arrayName, itemIndex); - DeleteProperty(schemaNS, itemPath); - } - catch (XMPException) - { - } - } - - // EMPTY, exceptions are ignored within delete - /// - public virtual void DeleteProperty(string schemaNS, string propName) - { - try - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); - XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); - if (propNode != null) - { - XMPNodeUtils.DeleteNode(propNode); - } - } - catch (XMPException) - { - } - } - - // EMPTY, exceptions are ignored within delete - /// - public virtual void DeleteQualifier(string schemaNS, string propName, string qualNS, string qualName) - { - try - { - // Note: qualNS and qualName are checked inside composeQualfierPath - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - string qualPath = propName + XMPPathFactory.ComposeQualifierPath(qualNS, qualName); - DeleteProperty(schemaNS, qualPath); - } - catch (XMPException) - { - } - } - - // EMPTY, exceptions within delete are ignored - /// - public virtual void DeleteStructField(string schemaNS, string structName, string fieldNS, string fieldName) - { - try - { - // fieldNS and fieldName are checked inside composeStructFieldPath - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertStructName(structName); - string fieldPath = structName + XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); - DeleteProperty(schemaNS, fieldPath); - } - catch (XMPException) - { - } - } - - // EMPTY, exceptions within delete are ignored - /// - public virtual bool DoesPropertyExist(string schemaNS, string propName) - { - try - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); - XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); - return propNode != null; - } - catch (XMPException) - { - return false; - } - } - - /// - public virtual bool DoesArrayItemExist(string schemaNS, string arrayName, int itemIndex) - { - try - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - string path = XMPPathFactory.ComposeArrayItemPath(arrayName, itemIndex); - return DoesPropertyExist(schemaNS, path); - } - catch (XMPException) - { - return false; - } - } - - /// - public virtual bool DoesStructFieldExist(string schemaNS, string structName, string fieldNS, string fieldName) - { - try - { - // fieldNS and fieldName are checked inside composeStructFieldPath() - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertStructName(structName); - string path = XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); - return DoesPropertyExist(schemaNS, structName + path); - } - catch (XMPException) - { - return false; - } - } - - /// - public virtual bool DoesQualifierExist(string schemaNS, string propName, string qualNS, string qualName) - { - try - { - // qualNS and qualName are checked inside composeQualifierPath() - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - string path = XMPPathFactory.ComposeQualifierPath(qualNS, qualName); - return DoesPropertyExist(schemaNS, propName + path); - } - catch (XMPException) - { - return false; - } - } - - /// - /// - public virtual XMPProperty GetArrayItem(string schemaNS, string arrayName, int itemIndex) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - string itemPath = XMPPathFactory.ComposeArrayItemPath(arrayName, itemIndex); - return GetProperty(schemaNS, itemPath); - } - - /// - /// - public virtual XMPProperty GetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(altTextName); - ParameterAsserts.AssertSpecificLang(specificLang); - genericLang = genericLang != null ? Utils.NormalizeLangValue(genericLang) : null; - specificLang = Utils.NormalizeLangValue(specificLang); - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, altTextName); - XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); - if (arrayNode == null) - { - return null; - } - object[] result = XMPNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang); - int match = ((int)result[0]).IntValue(); - XMPNode itemNode = (XMPNode)result[1]; - if (match != XMPNodeUtils.CltNoValues) - { - return new _XMPProperty_407(itemNode); - } - else - { - return null; - } - } - - private sealed class _XMPProperty_407 : XMPProperty - { - public _XMPProperty_407(XMPNode itemNode) - { - this.itemNode = itemNode; - } - - public string GetValue() - { - return itemNode.GetValue(); - } - - public PropertyOptions GetOptions() - { - return itemNode.GetOptions(); - } - - public string GetLanguage() - { - return itemNode.GetQualifier(1).GetValue(); - } - - public override string ToString() - { - return itemNode.GetValue().ToString(); - } - - private readonly XMPNode itemNode; - } - - /// - /// - public virtual void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue, PropertyOptions options) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(altTextName); - ParameterAsserts.AssertSpecificLang(specificLang); - genericLang = genericLang != null ? Utils.NormalizeLangValue(genericLang) : null; - specificLang = Utils.NormalizeLangValue(specificLang); - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, altTextName); - // Find the array node and set the options if it was just created. - XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, true, new PropertyOptions(PropertyOptions.Array | PropertyOptions.ArrayOrdered | PropertyOptions.ArrayAlternate | PropertyOptions.ArrayAltText)); - if (arrayNode == null) - { - throw new XMPException("Failed to find or create array node", XMPErrorConstants.Badxpath); - } - else - { - if (!arrayNode.GetOptions().IsArrayAltText()) - { - if (!arrayNode.HasChildren() && arrayNode.GetOptions().IsArrayAlternate()) - { - arrayNode.GetOptions().SetArrayAltText(true); - } - else - { - throw new XMPException("Specified property is no alt-text array", XMPErrorConstants.Badxpath); - } - } - } - // Make sure the x-default item, if any, is first. - bool haveXDefault = false; - XMPNode xdItem = null; - for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) - { - XMPNode currItem = (XMPNode)it.Next(); - if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) - { - throw new XMPException("Language qualifier must be first", XMPErrorConstants.Badxpath); - } - else - { - if (XMPConstConstants.XDefault.Equals(currItem.GetQualifier(1).GetValue())) - { - xdItem = currItem; - haveXDefault = true; - break; - } - } - } - // Moves x-default to the beginning of the array - if (xdItem != null && arrayNode.GetChildrenLength() > 1) - { - arrayNode.RemoveChild(xdItem); - arrayNode.AddChild(1, xdItem); - } - // Find the appropriate item. - // chooseLocalizedText will make sure the array is a language - // alternative. - object[] result = XMPNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang); - int match = ((int)result[0]).IntValue(); - XMPNode itemNode = (XMPNode)result[1]; - bool specificXDefault = XMPConstConstants.XDefault.Equals(specificLang); - switch (match) - { - case XMPNodeUtils.CltNoValues: - { - // Create the array items for the specificLang and x-default, with - // x-default first. - XMPNodeUtils.AppendLangItem(arrayNode, XMPConstConstants.XDefault, itemValue); - haveXDefault = true; - if (!specificXDefault) - { - XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); - } - break; - } - - case XMPNodeUtils.CltSpecificMatch: - { - if (!specificXDefault) - { - // Update the specific item, update x-default if it matches the - // old value. - if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.GetValue().Equals(itemNode.GetValue())) - { - xdItem.SetValue(itemValue); - } - // ! Do this after the x-default check! - itemNode.SetValue(itemValue); - } - else - { - // Update all items whose values match the old x-default value. - Debug.Assert(haveXDefault && xdItem == itemNode); - for (Iterator it_1 = arrayNode.IterateChildren(); it_1.HasNext(); ) - { - XMPNode currItem = (XMPNode)it_1.Next(); - if (currItem == xdItem || !currItem.GetValue().Equals(xdItem != null ? xdItem.GetValue() : null)) - { - continue; - } - currItem.SetValue(itemValue); - } - // And finally do the x-default item. - if (xdItem != null) - { - xdItem.SetValue(itemValue); - } - } - break; - } - - case XMPNodeUtils.CltSingleGeneric: - { - // Update the generic item, update x-default if it matches the old - // value. - if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.GetValue().Equals(itemNode.GetValue())) - { - xdItem.SetValue(itemValue); - } - itemNode.SetValue(itemValue); - // ! Do this after - // the x-default - // check! - break; - } - - case XMPNodeUtils.CltMultipleGeneric: - { - // Create the specific language, ignore x-default. - XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); - if (specificXDefault) - { - haveXDefault = true; - } - break; - } - - case XMPNodeUtils.CltXdefault: - { - // Create the specific language, update x-default if it was the only - // item. - if (xdItem != null && arrayNode.GetChildrenLength() == 1) - { - xdItem.SetValue(itemValue); - } - XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); - break; - } - - case XMPNodeUtils.CltFirstItem: - { - // Create the specific language, don't add an x-default item. - XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); - if (specificXDefault) - { - haveXDefault = true; - } - break; - } - - default: - { - // does not happen under normal circumstances - throw new XMPException("Unexpected result from ChooseLocalizedText", XMPErrorConstants.Internalfailure); - } - } - // Add an x-default at the front if needed. - if (!haveXDefault && arrayNode.GetChildrenLength() == 1) - { - XMPNodeUtils.AppendLangItem(arrayNode, XMPConstConstants.XDefault, itemValue); - } - } - - /// - /// - public virtual void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue) - { - SetLocalizedText(schemaNS, altTextName, genericLang, specificLang, itemValue, null); - } - - /// - /// - public virtual XMPProperty GetProperty(string schemaNS, string propName) - { - return GetProperty(schemaNS, propName, ValueString); - } - - /// Returns a property, but the result value can be requested. - /// - /// Returns a property, but the result value can be requested. It can be one - /// of - /// - /// , - /// - /// , - /// - /// , - /// - /// , - /// - /// , - /// - /// , - /// - /// , - /// - /// . - /// - /// - /// a schema namespace - /// a property name or path - /// the type of the value, see VALUE_... - /// Returns an XMPProperty - /// Collects any exception that occurs. - protected internal virtual XMPProperty GetProperty(string schemaNS, string propName, int valueType) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); - XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); - if (propNode != null) - { - if (valueType != ValueString && propNode.GetOptions().IsCompositeProperty()) - { - throw new XMPException("Property must be simple when a value type is requested", XMPErrorConstants.Badxpath); - } - object value = EvaluateNodeValue(valueType, propNode); - return new _XMPProperty_682(value, propNode); - } - else - { - return null; - } - } - - private sealed class _XMPProperty_682 : XMPProperty - { - public _XMPProperty_682(object value, XMPNode propNode) - { - this.value = value; - this.propNode = propNode; - } - - public string GetValue() - { - return value != null ? value.ToString() : null; - } - - public PropertyOptions GetOptions() - { - return propNode.GetOptions(); - } - - public string GetLanguage() - { - return null; - } - - public override string ToString() - { - return value.ToString(); - } - - private readonly object value; - - private readonly XMPNode propNode; - } - - /// Returns a property, but the result value can be requested. - /// - /// a schema namespace - /// a property name or path - /// the type of the value, see VALUE_... - /// - /// Returns the node value as an object according to the - /// valueType. - /// - /// Collects any exception that occurs. - protected internal virtual object GetPropertyObject(string schemaNS, string propName, int valueType) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); - XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); - if (propNode != null) - { - if (valueType != ValueString && propNode.GetOptions().IsCompositeProperty()) - { - throw new XMPException("Property must be simple when a value type is requested", XMPErrorConstants.Badxpath); - } - return EvaluateNodeValue(valueType, propNode); - } - else - { - return null; - } - } - - /// - /// - public virtual bool GetPropertyBoolean(string schemaNS, string propName) - { - return (bool)GetPropertyObject(schemaNS, propName, ValueBoolean); - } - - /// - /// - public virtual void SetPropertyBoolean(string schemaNS, string propName, bool propValue, PropertyOptions options) - { - SetProperty(schemaNS, propName, propValue ? XMPConstConstants.Truestr : XMPConstConstants.Falsestr, options); - } - - /// - /// - public virtual void SetPropertyBoolean(string schemaNS, string propName, bool propValue) - { - SetProperty(schemaNS, propName, propValue ? XMPConstConstants.Truestr : XMPConstConstants.Falsestr, null); - } - - /// - /// - public virtual int GetPropertyInteger(string schemaNS, string propName) - { - return (int)GetPropertyObject(schemaNS, propName, ValueInteger); - } - - /// - /// - public virtual void SetPropertyInteger(string schemaNS, string propName, int propValue, PropertyOptions options) - { - SetProperty(schemaNS, propName, propValue, options); - } - - /// - /// - public virtual void SetPropertyInteger(string schemaNS, string propName, int propValue) - { - SetProperty(schemaNS, propName, propValue, null); - } - - /// - /// - public virtual long GetPropertyLong(string schemaNS, string propName) - { - return (long)GetPropertyObject(schemaNS, propName, ValueLong); - } - - /// - /// - public virtual void SetPropertyLong(string schemaNS, string propName, long propValue, PropertyOptions options) - { - SetProperty(schemaNS, propName, propValue, options); - } - - /// - /// - public virtual void SetPropertyLong(string schemaNS, string propName, long propValue) - { - SetProperty(schemaNS, propName, propValue, null); - } - - /// - /// - public virtual double GetPropertyDouble(string schemaNS, string propName) - { - return (double)GetPropertyObject(schemaNS, propName, ValueDouble); - } - - /// - /// - public virtual void SetPropertyDouble(string schemaNS, string propName, double propValue, PropertyOptions options) - { - SetProperty(schemaNS, propName, propValue, options); - } - - /// - /// - public virtual void SetPropertyDouble(string schemaNS, string propName, double propValue) - { - SetProperty(schemaNS, propName, propValue, null); - } - - /// - /// - public virtual XMPDateTime GetPropertyDate(string schemaNS, string propName) - { - return (XMPDateTime)GetPropertyObject(schemaNS, propName, ValueDate); - } - - /// - /// - public virtual void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue, PropertyOptions options) - { - SetProperty(schemaNS, propName, propValue, options); - } - - /// - /// - public virtual void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue) - { - SetProperty(schemaNS, propName, propValue, null); - } - - /// - /// - public virtual Calendar GetPropertyCalendar(string schemaNS, string propName) - { - return (Calendar)GetPropertyObject(schemaNS, propName, ValueCalendar); - } - - /// - /// - public virtual void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue, PropertyOptions options) - { - SetProperty(schemaNS, propName, propValue, options); - } - - /// - /// - public virtual void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue) - { - SetProperty(schemaNS, propName, propValue, null); - } - - /// - /// - public virtual sbyte[] GetPropertyBase64(string schemaNS, string propName) - { - return (sbyte[])GetPropertyObject(schemaNS, propName, ValueBase64); - } - - /// - /// - public virtual string GetPropertyString(string schemaNS, string propName) - { - return (string)GetPropertyObject(schemaNS, propName, ValueString); - } - - /// - /// - public virtual void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue, PropertyOptions options) - { - SetProperty(schemaNS, propName, propValue, options); - } - - /// - /// - public virtual void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue) - { - SetProperty(schemaNS, propName, propValue, null); - } - - /// - /// - public virtual XMPProperty GetQualifier(string schemaNS, string propName, string qualNS, string qualName) - { - // qualNS and qualName are checked inside composeQualfierPath - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - string qualPath = propName + XMPPathFactory.ComposeQualifierPath(qualNS, qualName); - return GetProperty(schemaNS, qualPath); - } - - /// - /// - public virtual XMPProperty GetStructField(string schemaNS, string structName, string fieldNS, string fieldName) - { - // fieldNS and fieldName are checked inside composeStructFieldPath - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertStructName(structName); - string fieldPath = structName + XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); - return GetProperty(schemaNS, fieldPath); - } - - /// - /// - public virtual XMPIterator Iterator() - { - return Iterator(null, null, null); - } - - /// - /// - public virtual XMPIterator Iterator(IteratorOptions options) - { - return Iterator(null, null, options); - } - - /// - /// - public virtual XMPIterator Iterator(string schemaNS, string propName, IteratorOptions options) - { - return new XMPIteratorImpl(this, schemaNS, propName, options); - } - - /// - /// - public virtual void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - // Just lookup, don't try to create. - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); - XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); - if (arrayNode != null) - { - DoSetArrayItem(arrayNode, itemIndex, itemValue, options, false); - } - else - { - throw new XMPException("Specified array does not exist", XMPErrorConstants.Badxpath); - } - } - - /// - /// - public virtual void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue) - { - SetArrayItem(schemaNS, arrayName, itemIndex, itemValue, null); - } - - /// - /// - public virtual void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - // Just lookup, don't try to create. - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); - XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); - if (arrayNode != null) - { - DoSetArrayItem(arrayNode, itemIndex, itemValue, options, true); - } - else - { - throw new XMPException("Specified array does not exist", XMPErrorConstants.Badxpath); - } - } - - /// - /// - public virtual void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue) - { - InsertArrayItem(schemaNS, arrayName, itemIndex, itemValue, null); - } - - /// - /// - public virtual void SetProperty(string schemaNS, string propName, object propValue, PropertyOptions options) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - options = XMPNodeUtils.VerifySetOptions(options, propValue); - XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); - XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, true, options); - if (propNode != null) - { - SetNode(propNode, propValue, options, false); - } - else - { - throw new XMPException("Specified property does not exist", XMPErrorConstants.Badxpath); - } - } - - /// - /// - public virtual void SetProperty(string schemaNS, string propName, object propValue) - { - SetProperty(schemaNS, propName, propValue, null); - } - - /// - /// - public virtual void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue, PropertyOptions options) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertPropName(propName); - if (!DoesPropertyExist(schemaNS, propName)) - { - throw new XMPException("Specified property does not exist!", XMPErrorConstants.Badxpath); - } - string qualPath = propName + XMPPathFactory.ComposeQualifierPath(qualNS, qualName); - SetProperty(schemaNS, qualPath, qualValue, options); - } - - /// - /// - public virtual void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue) - { - SetQualifier(schemaNS, propName, qualNS, qualName, qualValue, null); - } - - /// - /// - public virtual void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue, PropertyOptions options) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertStructName(structName); - string fieldPath = structName + XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); - SetProperty(schemaNS, fieldPath, fieldValue, options); - } - - /// - /// - public virtual void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue) - { - SetStructField(schemaNS, structName, fieldNS, fieldName, fieldValue, null); - } - - /// - public virtual string GetObjectName() - { - return tree.GetName() != null ? tree.GetName() : string.Empty; - } - - /// - public virtual void SetObjectName(string name) - { - tree.SetName(name); - } - - /// - public virtual string GetPacketHeader() - { - return packetHeader; - } - - /// Sets the packetHeader attributes, only used by the parser. - /// the processing instruction content - public virtual void SetPacketHeader(string packetHeader) - { - this.packetHeader = packetHeader; - } - - /// Performs a deep clone of the XMPMeta-object - /// - public virtual object Clone() - { - XMPNode clonedTree = (XMPNode)tree.Clone(); - return new XMPMetaImpl(clonedTree); - } - - /// - public virtual string DumpObject() - { - // renders tree recursively - return GetRoot().DumpNode(true); - } - - /// - public virtual void Sort() - { - this.tree.Sort(); - } - - /// - /// - public virtual void Normalize(ParseOptions options) - { - if (options == null) - { - options = new ParseOptions(); - } - XMPNormalizer.Process(this, options); - } - - /// Returns the root node of the XMP tree. - public virtual XMPNode GetRoot() - { - return tree; - } - - // ------------------------------------------------------------------------------------- - // private - /// Locate or create the item node and set the value. - /// - /// Locate or create the item node and set the value. Note the index - /// parameter is one-based! The index can be in the range [1..size + 1] or - /// "last()", normalize it and check the insert flags. The order of the - /// normalization checks is important. If the array is empty we end up with - /// an index and location to set item size + 1. - /// - /// an array node - /// the index where to insert the item - /// the item value - /// the options for the new item - /// insert oder overwrite at index position? - /// - private void DoSetArrayItem(XMPNode arrayNode, int itemIndex, string itemValue, PropertyOptions itemOptions, bool insert) - { - XMPNode itemNode = new XMPNode(XMPConstConstants.ArrayItemName, null); - itemOptions = XMPNodeUtils.VerifySetOptions(itemOptions, itemValue); - // in insert mode the index after the last is allowed, - // even ARRAY_LAST_ITEM points to the index *after* the last. - int maxIndex = insert ? arrayNode.GetChildrenLength() + 1 : arrayNode.GetChildrenLength(); - if (itemIndex == XMPConstConstants.ArrayLastItem) - { - itemIndex = maxIndex; - } - if (1 <= itemIndex && itemIndex <= maxIndex) - { - if (!insert) - { - arrayNode.RemoveChild(itemIndex); - } - arrayNode.AddChild(itemIndex, itemNode); - SetNode(itemNode, itemValue, itemOptions, false); - } - else - { - throw new XMPException("Array index out of bounds", XMPErrorConstants.Badindex); - } - } - - /// - /// The internals for setProperty() and related calls, used after the node is - /// found or created. - /// - /// the newly created node - /// the node value, can be null - /// options for the new node, must not be null. - /// flag if the existing value is to be overwritten - /// thrown if options and value do not correspond - internal virtual void SetNode(XMPNode node, object value, PropertyOptions newOptions, bool deleteExisting) - { - if (deleteExisting) - { - node.Clear(); - } - // its checked by setOptions(), if the merged result is a valid options set - node.GetOptions().MergeWith(newOptions); - if (!node.GetOptions().IsCompositeProperty()) - { - // This is setting the value of a leaf node. - XMPNodeUtils.SetNodeValue(node, value); - } - else - { - if (value != null && value.ToString().Length > 0) - { - throw new XMPException("Composite nodes can't have values", XMPErrorConstants.Badxpath); - } - node.RemoveChildren(); - } - } - - /// - /// Evaluates a raw node value to the given value type, apply special - /// conversions for defined types in XMP. - /// - /// an int indicating the value type - /// the node containing the value - /// Returns a literal value for the node. - /// - private object EvaluateNodeValue(int valueType, XMPNode propNode) - { - object value; - string rawValue = propNode.GetValue(); - switch (valueType) - { - case ValueBoolean: - { - value = XMPUtils.ConvertToBoolean(rawValue); - break; - } - - case ValueInteger: - { - value = XMPUtils.ConvertToInteger(rawValue); - break; - } - - case ValueLong: - { - value = XMPUtils.ConvertToLong(rawValue); - break; - } - - case ValueDouble: - { - value = XMPUtils.ConvertToDouble(rawValue); - break; - } - - case ValueDate: - { - value = XMPUtils.ConvertToDate(rawValue); - break; - } - - case ValueCalendar: - { - XMPDateTime dt = XMPUtils.ConvertToDate(rawValue); - value = dt.GetCalendar(); - break; - } - - case ValueBase64: - { - value = XMPUtils.DecodeBase64(rawValue); - break; - } - - case ValueString: - default: - { - // leaf values return empty string instead of null - // for the other cases the converter methods provides a "null" - // value. - // a default value can only occur if this method is made public. - value = rawValue != null || propNode.GetOptions().IsCompositeProperty() ? rawValue : string.Empty; - break; - } - } - return value; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Diagnostics; +using Com.Adobe.Xmp.Impl.Xpath; +using Com.Adobe.Xmp.Options; +using Com.Adobe.Xmp.Properties; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// + /// Implementation for + /// + /// . + /// + /// 17.02.2006 + public class XMPMetaImpl : XMPMeta, XMPConst + { + /// Property values are Strings by default + private const int ValueString = 0; + + private const int ValueBoolean = 1; + + private const int ValueInteger = 2; + + private const int ValueLong = 3; + + private const int ValueDouble = 4; + + private const int ValueDate = 5; + + private const int ValueCalendar = 6; + + private const int ValueBase64 = 7; + + /// root of the metadata tree + private XMPNode tree; + + /// the xpacket processing instructions content + private string packetHeader = null; + + /// Constructor for an empty metadata object. + public XMPMetaImpl() + { + // create root node + tree = new XMPNode(null, null, null); + } + + /// Constructor for a cloned metadata tree. + /// + /// an prefilled metadata tree which fulfills all + /// XMPNode contracts. + /// + public XMPMetaImpl(XMPNode tree) + { + this.tree = tree; + } + + /// + /// + public virtual void AppendArrayItem(string schemaNS, string arrayName, PropertyOptions arrayOptions, string itemValue, PropertyOptions itemOptions) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + if (arrayOptions == null) + { + arrayOptions = new PropertyOptions(); + } + if (!arrayOptions.IsOnlyArrayOptions()) + { + throw new XMPException("Only array form flags allowed for arrayOptions", XMPErrorConstants.Badoptions); + } + // Check if array options are set correctly. + arrayOptions = XMPNodeUtils.VerifySetOptions(arrayOptions, null); + // Locate or create the array. If it already exists, make sure the array + // form from the options + // parameter is compatible with the current state. + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); + // Just lookup, don't try to create. + XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); + if (arrayNode != null) + { + // The array exists, make sure the form is compatible. Zero + // arrayForm means take what exists. + if (!arrayNode.GetOptions().IsArray()) + { + throw new XMPException("The named property is not an array", XMPErrorConstants.Badxpath); + } + } + else + { + // if (arrayOptions != null && !arrayOptions.equalArrayTypes(arrayNode.getOptions())) + // { + // throw new XMPException("Mismatch of existing and specified array form", BADOPTIONS); + // } + // The array does not exist, try to create it. + if (arrayOptions.IsArray()) + { + arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, true, arrayOptions); + if (arrayNode == null) + { + throw new XMPException("Failure creating array node", XMPErrorConstants.Badxpath); + } + } + else + { + // array options missing + throw new XMPException("Explicit arrayOptions required to create new array", XMPErrorConstants.Badoptions); + } + } + DoSetArrayItem(arrayNode, XMPConstConstants.ArrayLastItem, itemValue, itemOptions, true); + } + + /// + /// + public virtual void AppendArrayItem(string schemaNS, string arrayName, string itemValue) + { + AppendArrayItem(schemaNS, arrayName, null, itemValue, null); + } + + /// + /// + public virtual int CountArrayItems(string schemaNS, string arrayName) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); + XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); + if (arrayNode == null) + { + return 0; + } + if (arrayNode.GetOptions().IsArray()) + { + return arrayNode.GetChildrenLength(); + } + else + { + throw new XMPException("The named property is not an array", XMPErrorConstants.Badxpath); + } + } + + /// + public virtual void DeleteArrayItem(string schemaNS, string arrayName, int itemIndex) + { + try + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + string itemPath = XMPPathFactory.ComposeArrayItemPath(arrayName, itemIndex); + DeleteProperty(schemaNS, itemPath); + } + catch (XMPException) + { + } + } + + // EMPTY, exceptions are ignored within delete + /// + public virtual void DeleteProperty(string schemaNS, string propName) + { + try + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); + XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); + if (propNode != null) + { + XMPNodeUtils.DeleteNode(propNode); + } + } + catch (XMPException) + { + } + } + + // EMPTY, exceptions are ignored within delete + /// + public virtual void DeleteQualifier(string schemaNS, string propName, string qualNS, string qualName) + { + try + { + // Note: qualNS and qualName are checked inside composeQualfierPath + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + string qualPath = propName + XMPPathFactory.ComposeQualifierPath(qualNS, qualName); + DeleteProperty(schemaNS, qualPath); + } + catch (XMPException) + { + } + } + + // EMPTY, exceptions within delete are ignored + /// + public virtual void DeleteStructField(string schemaNS, string structName, string fieldNS, string fieldName) + { + try + { + // fieldNS and fieldName are checked inside composeStructFieldPath + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertStructName(structName); + string fieldPath = structName + XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); + DeleteProperty(schemaNS, fieldPath); + } + catch (XMPException) + { + } + } + + // EMPTY, exceptions within delete are ignored + /// + public virtual bool DoesPropertyExist(string schemaNS, string propName) + { + try + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); + XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); + return propNode != null; + } + catch (XMPException) + { + return false; + } + } + + /// + public virtual bool DoesArrayItemExist(string schemaNS, string arrayName, int itemIndex) + { + try + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + string path = XMPPathFactory.ComposeArrayItemPath(arrayName, itemIndex); + return DoesPropertyExist(schemaNS, path); + } + catch (XMPException) + { + return false; + } + } + + /// + public virtual bool DoesStructFieldExist(string schemaNS, string structName, string fieldNS, string fieldName) + { + try + { + // fieldNS and fieldName are checked inside composeStructFieldPath() + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertStructName(structName); + string path = XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); + return DoesPropertyExist(schemaNS, structName + path); + } + catch (XMPException) + { + return false; + } + } + + /// + public virtual bool DoesQualifierExist(string schemaNS, string propName, string qualNS, string qualName) + { + try + { + // qualNS and qualName are checked inside composeQualifierPath() + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + string path = XMPPathFactory.ComposeQualifierPath(qualNS, qualName); + return DoesPropertyExist(schemaNS, propName + path); + } + catch (XMPException) + { + return false; + } + } + + /// + /// + public virtual XMPProperty GetArrayItem(string schemaNS, string arrayName, int itemIndex) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + string itemPath = XMPPathFactory.ComposeArrayItemPath(arrayName, itemIndex); + return GetProperty(schemaNS, itemPath); + } + + /// + /// + public virtual XMPProperty GetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(altTextName); + ParameterAsserts.AssertSpecificLang(specificLang); + genericLang = genericLang != null ? Utils.NormalizeLangValue(genericLang) : null; + specificLang = Utils.NormalizeLangValue(specificLang); + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, altTextName); + XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); + if (arrayNode == null) + { + return null; + } + object[] result = XMPNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang); + int match = ((int)result[0]).IntValue(); + XMPNode itemNode = (XMPNode)result[1]; + if (match != XMPNodeUtils.CltNoValues) + { + return new _XMPProperty_407(itemNode); + } + else + { + return null; + } + } + + private sealed class _XMPProperty_407 : XMPProperty + { + public _XMPProperty_407(XMPNode itemNode) + { + this.itemNode = itemNode; + } + + public string GetValue() + { + return itemNode.GetValue(); + } + + public PropertyOptions GetOptions() + { + return itemNode.GetOptions(); + } + + public string GetLanguage() + { + return itemNode.GetQualifier(1).GetValue(); + } + + public override string ToString() + { + return itemNode.GetValue().ToString(); + } + + private readonly XMPNode itemNode; + } + + /// + /// + public virtual void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue, PropertyOptions options) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(altTextName); + ParameterAsserts.AssertSpecificLang(specificLang); + genericLang = genericLang != null ? Utils.NormalizeLangValue(genericLang) : null; + specificLang = Utils.NormalizeLangValue(specificLang); + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, altTextName); + // Find the array node and set the options if it was just created. + XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, true, new PropertyOptions(PropertyOptions.Array | PropertyOptions.ArrayOrdered | PropertyOptions.ArrayAlternate | PropertyOptions.ArrayAltText)); + if (arrayNode == null) + { + throw new XMPException("Failed to find or create array node", XMPErrorConstants.Badxpath); + } + else + { + if (!arrayNode.GetOptions().IsArrayAltText()) + { + if (!arrayNode.HasChildren() && arrayNode.GetOptions().IsArrayAlternate()) + { + arrayNode.GetOptions().SetArrayAltText(true); + } + else + { + throw new XMPException("Specified property is no alt-text array", XMPErrorConstants.Badxpath); + } + } + } + // Make sure the x-default item, if any, is first. + bool haveXDefault = false; + XMPNode xdItem = null; + for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) + { + XMPNode currItem = (XMPNode)it.Next(); + if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) + { + throw new XMPException("Language qualifier must be first", XMPErrorConstants.Badxpath); + } + else + { + if (XMPConstConstants.XDefault.Equals(currItem.GetQualifier(1).GetValue())) + { + xdItem = currItem; + haveXDefault = true; + break; + } + } + } + // Moves x-default to the beginning of the array + if (xdItem != null && arrayNode.GetChildrenLength() > 1) + { + arrayNode.RemoveChild(xdItem); + arrayNode.AddChild(1, xdItem); + } + // Find the appropriate item. + // chooseLocalizedText will make sure the array is a language + // alternative. + object[] result = XMPNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang); + int match = ((int)result[0]).IntValue(); + XMPNode itemNode = (XMPNode)result[1]; + bool specificXDefault = XMPConstConstants.XDefault.Equals(specificLang); + switch (match) + { + case XMPNodeUtils.CltNoValues: + { + // Create the array items for the specificLang and x-default, with + // x-default first. + XMPNodeUtils.AppendLangItem(arrayNode, XMPConstConstants.XDefault, itemValue); + haveXDefault = true; + if (!specificXDefault) + { + XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); + } + break; + } + + case XMPNodeUtils.CltSpecificMatch: + { + if (!specificXDefault) + { + // Update the specific item, update x-default if it matches the + // old value. + if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.GetValue().Equals(itemNode.GetValue())) + { + xdItem.SetValue(itemValue); + } + // ! Do this after the x-default check! + itemNode.SetValue(itemValue); + } + else + { + // Update all items whose values match the old x-default value. + Debug.Assert(haveXDefault && xdItem == itemNode); + for (Iterator it_1 = arrayNode.IterateChildren(); it_1.HasNext(); ) + { + XMPNode currItem = (XMPNode)it_1.Next(); + if (currItem == xdItem || !currItem.GetValue().Equals(xdItem != null ? xdItem.GetValue() : null)) + { + continue; + } + currItem.SetValue(itemValue); + } + // And finally do the x-default item. + if (xdItem != null) + { + xdItem.SetValue(itemValue); + } + } + break; + } + + case XMPNodeUtils.CltSingleGeneric: + { + // Update the generic item, update x-default if it matches the old + // value. + if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.GetValue().Equals(itemNode.GetValue())) + { + xdItem.SetValue(itemValue); + } + itemNode.SetValue(itemValue); + // ! Do this after + // the x-default + // check! + break; + } + + case XMPNodeUtils.CltMultipleGeneric: + { + // Create the specific language, ignore x-default. + XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); + if (specificXDefault) + { + haveXDefault = true; + } + break; + } + + case XMPNodeUtils.CltXdefault: + { + // Create the specific language, update x-default if it was the only + // item. + if (xdItem != null && arrayNode.GetChildrenLength() == 1) + { + xdItem.SetValue(itemValue); + } + XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); + break; + } + + case XMPNodeUtils.CltFirstItem: + { + // Create the specific language, don't add an x-default item. + XMPNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); + if (specificXDefault) + { + haveXDefault = true; + } + break; + } + + default: + { + // does not happen under normal circumstances + throw new XMPException("Unexpected result from ChooseLocalizedText", XMPErrorConstants.Internalfailure); + } + } + // Add an x-default at the front if needed. + if (!haveXDefault && arrayNode.GetChildrenLength() == 1) + { + XMPNodeUtils.AppendLangItem(arrayNode, XMPConstConstants.XDefault, itemValue); + } + } + + /// + /// + public virtual void SetLocalizedText(string schemaNS, string altTextName, string genericLang, string specificLang, string itemValue) + { + SetLocalizedText(schemaNS, altTextName, genericLang, specificLang, itemValue, null); + } + + /// + /// + public virtual XMPProperty GetProperty(string schemaNS, string propName) + { + return GetProperty(schemaNS, propName, ValueString); + } + + /// Returns a property, but the result value can be requested. + /// + /// Returns a property, but the result value can be requested. It can be one + /// of + /// + /// , + /// + /// , + /// + /// , + /// + /// , + /// + /// , + /// + /// , + /// + /// , + /// + /// . + /// + /// + /// a schema namespace + /// a property name or path + /// the type of the value, see VALUE_... + /// Returns an XMPProperty + /// Collects any exception that occurs. + protected internal virtual XMPProperty GetProperty(string schemaNS, string propName, int valueType) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); + XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); + if (propNode != null) + { + if (valueType != ValueString && propNode.GetOptions().IsCompositeProperty()) + { + throw new XMPException("Property must be simple when a value type is requested", XMPErrorConstants.Badxpath); + } + object value = EvaluateNodeValue(valueType, propNode); + return new _XMPProperty_682(value, propNode); + } + else + { + return null; + } + } + + private sealed class _XMPProperty_682 : XMPProperty + { + public _XMPProperty_682(object value, XMPNode propNode) + { + this.value = value; + this.propNode = propNode; + } + + public string GetValue() + { + return value != null ? value.ToString() : null; + } + + public PropertyOptions GetOptions() + { + return propNode.GetOptions(); + } + + public string GetLanguage() + { + return null; + } + + public override string ToString() + { + return value.ToString(); + } + + private readonly object value; + + private readonly XMPNode propNode; + } + + /// Returns a property, but the result value can be requested. + /// + /// a schema namespace + /// a property name or path + /// the type of the value, see VALUE_... + /// + /// Returns the node value as an object according to the + /// valueType. + /// + /// Collects any exception that occurs. + protected internal virtual object GetPropertyObject(string schemaNS, string propName, int valueType) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); + XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, false, null); + if (propNode != null) + { + if (valueType != ValueString && propNode.GetOptions().IsCompositeProperty()) + { + throw new XMPException("Property must be simple when a value type is requested", XMPErrorConstants.Badxpath); + } + return EvaluateNodeValue(valueType, propNode); + } + else + { + return null; + } + } + + /// + /// + public virtual bool GetPropertyBoolean(string schemaNS, string propName) + { + return (bool)GetPropertyObject(schemaNS, propName, ValueBoolean); + } + + /// + /// + public virtual void SetPropertyBoolean(string schemaNS, string propName, bool propValue, PropertyOptions options) + { + SetProperty(schemaNS, propName, propValue ? XMPConstConstants.Truestr : XMPConstConstants.Falsestr, options); + } + + /// + /// + public virtual void SetPropertyBoolean(string schemaNS, string propName, bool propValue) + { + SetProperty(schemaNS, propName, propValue ? XMPConstConstants.Truestr : XMPConstConstants.Falsestr, null); + } + + /// + /// + public virtual int GetPropertyInteger(string schemaNS, string propName) + { + return (int)GetPropertyObject(schemaNS, propName, ValueInteger); + } + + /// + /// + public virtual void SetPropertyInteger(string schemaNS, string propName, int propValue, PropertyOptions options) + { + SetProperty(schemaNS, propName, propValue, options); + } + + /// + /// + public virtual void SetPropertyInteger(string schemaNS, string propName, int propValue) + { + SetProperty(schemaNS, propName, propValue, null); + } + + /// + /// + public virtual long GetPropertyLong(string schemaNS, string propName) + { + return (long)GetPropertyObject(schemaNS, propName, ValueLong); + } + + /// + /// + public virtual void SetPropertyLong(string schemaNS, string propName, long propValue, PropertyOptions options) + { + SetProperty(schemaNS, propName, propValue, options); + } + + /// + /// + public virtual void SetPropertyLong(string schemaNS, string propName, long propValue) + { + SetProperty(schemaNS, propName, propValue, null); + } + + /// + /// + public virtual double GetPropertyDouble(string schemaNS, string propName) + { + return (double)GetPropertyObject(schemaNS, propName, ValueDouble); + } + + /// + /// + public virtual void SetPropertyDouble(string schemaNS, string propName, double propValue, PropertyOptions options) + { + SetProperty(schemaNS, propName, propValue, options); + } + + /// + /// + public virtual void SetPropertyDouble(string schemaNS, string propName, double propValue) + { + SetProperty(schemaNS, propName, propValue, null); + } + + /// + /// + public virtual XMPDateTime GetPropertyDate(string schemaNS, string propName) + { + return (XMPDateTime)GetPropertyObject(schemaNS, propName, ValueDate); + } + + /// + /// + public virtual void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue, PropertyOptions options) + { + SetProperty(schemaNS, propName, propValue, options); + } + + /// + /// + public virtual void SetPropertyDate(string schemaNS, string propName, XMPDateTime propValue) + { + SetProperty(schemaNS, propName, propValue, null); + } + + /// + /// + public virtual Calendar GetPropertyCalendar(string schemaNS, string propName) + { + return (Calendar)GetPropertyObject(schemaNS, propName, ValueCalendar); + } + + /// + /// + public virtual void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue, PropertyOptions options) + { + SetProperty(schemaNS, propName, propValue, options); + } + + /// + /// + public virtual void SetPropertyCalendar(string schemaNS, string propName, Calendar propValue) + { + SetProperty(schemaNS, propName, propValue, null); + } + + /// + /// + public virtual sbyte[] GetPropertyBase64(string schemaNS, string propName) + { + return (sbyte[])GetPropertyObject(schemaNS, propName, ValueBase64); + } + + /// + /// + public virtual string GetPropertyString(string schemaNS, string propName) + { + return (string)GetPropertyObject(schemaNS, propName, ValueString); + } + + /// + /// + public virtual void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue, PropertyOptions options) + { + SetProperty(schemaNS, propName, propValue, options); + } + + /// + /// + public virtual void SetPropertyBase64(string schemaNS, string propName, sbyte[] propValue) + { + SetProperty(schemaNS, propName, propValue, null); + } + + /// + /// + public virtual XMPProperty GetQualifier(string schemaNS, string propName, string qualNS, string qualName) + { + // qualNS and qualName are checked inside composeQualfierPath + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + string qualPath = propName + XMPPathFactory.ComposeQualifierPath(qualNS, qualName); + return GetProperty(schemaNS, qualPath); + } + + /// + /// + public virtual XMPProperty GetStructField(string schemaNS, string structName, string fieldNS, string fieldName) + { + // fieldNS and fieldName are checked inside composeStructFieldPath + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertStructName(structName); + string fieldPath = structName + XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); + return GetProperty(schemaNS, fieldPath); + } + + /// + /// + public virtual XMPIterator Iterator() + { + return Iterator(null, null, null); + } + + /// + /// + public virtual XMPIterator Iterator(IteratorOptions options) + { + return Iterator(null, null, options); + } + + /// + /// + public virtual XMPIterator Iterator(string schemaNS, string propName, IteratorOptions options) + { + return new XMPIteratorImpl(this, schemaNS, propName, options); + } + + /// + /// + public virtual void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + // Just lookup, don't try to create. + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); + XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); + if (arrayNode != null) + { + DoSetArrayItem(arrayNode, itemIndex, itemValue, options, false); + } + else + { + throw new XMPException("Specified array does not exist", XMPErrorConstants.Badxpath); + } + } + + /// + /// + public virtual void SetArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue) + { + SetArrayItem(schemaNS, arrayName, itemIndex, itemValue, null); + } + + /// + /// + public virtual void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue, PropertyOptions options) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + // Just lookup, don't try to create. + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); + XMPNode arrayNode = XMPNodeUtils.FindNode(tree, arrayPath, false, null); + if (arrayNode != null) + { + DoSetArrayItem(arrayNode, itemIndex, itemValue, options, true); + } + else + { + throw new XMPException("Specified array does not exist", XMPErrorConstants.Badxpath); + } + } + + /// + /// + public virtual void InsertArrayItem(string schemaNS, string arrayName, int itemIndex, string itemValue) + { + InsertArrayItem(schemaNS, arrayName, itemIndex, itemValue, null); + } + + /// + /// + public virtual void SetProperty(string schemaNS, string propName, object propValue, PropertyOptions options) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + options = XMPNodeUtils.VerifySetOptions(options, propValue); + XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); + XMPNode propNode = XMPNodeUtils.FindNode(tree, expPath, true, options); + if (propNode != null) + { + SetNode(propNode, propValue, options, false); + } + else + { + throw new XMPException("Specified property does not exist", XMPErrorConstants.Badxpath); + } + } + + /// + /// + public virtual void SetProperty(string schemaNS, string propName, object propValue) + { + SetProperty(schemaNS, propName, propValue, null); + } + + /// + /// + public virtual void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue, PropertyOptions options) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertPropName(propName); + if (!DoesPropertyExist(schemaNS, propName)) + { + throw new XMPException("Specified property does not exist!", XMPErrorConstants.Badxpath); + } + string qualPath = propName + XMPPathFactory.ComposeQualifierPath(qualNS, qualName); + SetProperty(schemaNS, qualPath, qualValue, options); + } + + /// + /// + public virtual void SetQualifier(string schemaNS, string propName, string qualNS, string qualName, string qualValue) + { + SetQualifier(schemaNS, propName, qualNS, qualName, qualValue, null); + } + + /// + /// + public virtual void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue, PropertyOptions options) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertStructName(structName); + string fieldPath = structName + XMPPathFactory.ComposeStructFieldPath(fieldNS, fieldName); + SetProperty(schemaNS, fieldPath, fieldValue, options); + } + + /// + /// + public virtual void SetStructField(string schemaNS, string structName, string fieldNS, string fieldName, string fieldValue) + { + SetStructField(schemaNS, structName, fieldNS, fieldName, fieldValue, null); + } + + /// + public virtual string GetObjectName() + { + return tree.GetName() != null ? tree.GetName() : string.Empty; + } + + /// + public virtual void SetObjectName(string name) + { + tree.SetName(name); + } + + /// + public virtual string GetPacketHeader() + { + return packetHeader; + } + + /// Sets the packetHeader attributes, only used by the parser. + /// the processing instruction content + public virtual void SetPacketHeader(string packetHeader) + { + this.packetHeader = packetHeader; + } + + /// Performs a deep clone of the XMPMeta-object + /// + public virtual object Clone() + { + XMPNode clonedTree = (XMPNode)tree.Clone(); + return new XMPMetaImpl(clonedTree); + } + + /// + public virtual string DumpObject() + { + // renders tree recursively + return GetRoot().DumpNode(true); + } + + /// + public virtual void Sort() + { + this.tree.Sort(); + } + + /// + /// + public virtual void Normalize(ParseOptions options) + { + if (options == null) + { + options = new ParseOptions(); + } + XMPNormalizer.Process(this, options); + } + + /// Returns the root node of the XMP tree. + public virtual XMPNode GetRoot() + { + return tree; + } + + // ------------------------------------------------------------------------------------- + // private + /// Locate or create the item node and set the value. + /// + /// Locate or create the item node and set the value. Note the index + /// parameter is one-based! The index can be in the range [1..size + 1] or + /// "last()", normalize it and check the insert flags. The order of the + /// normalization checks is important. If the array is empty we end up with + /// an index and location to set item size + 1. + /// + /// an array node + /// the index where to insert the item + /// the item value + /// the options for the new item + /// insert oder overwrite at index position? + /// + private void DoSetArrayItem(XMPNode arrayNode, int itemIndex, string itemValue, PropertyOptions itemOptions, bool insert) + { + XMPNode itemNode = new XMPNode(XMPConstConstants.ArrayItemName, null); + itemOptions = XMPNodeUtils.VerifySetOptions(itemOptions, itemValue); + // in insert mode the index after the last is allowed, + // even ARRAY_LAST_ITEM points to the index *after* the last. + int maxIndex = insert ? arrayNode.GetChildrenLength() + 1 : arrayNode.GetChildrenLength(); + if (itemIndex == XMPConstConstants.ArrayLastItem) + { + itemIndex = maxIndex; + } + if (1 <= itemIndex && itemIndex <= maxIndex) + { + if (!insert) + { + arrayNode.RemoveChild(itemIndex); + } + arrayNode.AddChild(itemIndex, itemNode); + SetNode(itemNode, itemValue, itemOptions, false); + } + else + { + throw new XMPException("Array index out of bounds", XMPErrorConstants.Badindex); + } + } + + /// + /// The internals for setProperty() and related calls, used after the node is + /// found or created. + /// + /// the newly created node + /// the node value, can be null + /// options for the new node, must not be null. + /// flag if the existing value is to be overwritten + /// thrown if options and value do not correspond + internal virtual void SetNode(XMPNode node, object value, PropertyOptions newOptions, bool deleteExisting) + { + if (deleteExisting) + { + node.Clear(); + } + // its checked by setOptions(), if the merged result is a valid options set + node.GetOptions().MergeWith(newOptions); + if (!node.GetOptions().IsCompositeProperty()) + { + // This is setting the value of a leaf node. + XMPNodeUtils.SetNodeValue(node, value); + } + else + { + if (value != null && value.ToString().Length > 0) + { + throw new XMPException("Composite nodes can't have values", XMPErrorConstants.Badxpath); + } + node.RemoveChildren(); + } + } + + /// + /// Evaluates a raw node value to the given value type, apply special + /// conversions for defined types in XMP. + /// + /// an int indicating the value type + /// the node containing the value + /// Returns a literal value for the node. + /// + private object EvaluateNodeValue(int valueType, XMPNode propNode) + { + object value; + string rawValue = propNode.GetValue(); + switch (valueType) + { + case ValueBoolean: + { + value = XMPUtils.ConvertToBoolean(rawValue); + break; + } + + case ValueInteger: + { + value = XMPUtils.ConvertToInteger(rawValue); + break; + } + + case ValueLong: + { + value = XMPUtils.ConvertToLong(rawValue); + break; + } + + case ValueDouble: + { + value = XMPUtils.ConvertToDouble(rawValue); + break; + } + + case ValueDate: + { + value = XMPUtils.ConvertToDate(rawValue); + break; + } + + case ValueCalendar: + { + XMPDateTime dt = XMPUtils.ConvertToDate(rawValue); + value = dt.GetCalendar(); + break; + } + + case ValueBase64: + { + value = XMPUtils.DecodeBase64(rawValue); + break; + } + + case ValueString: + default: + { + // leaf values return empty string instead of null + // for the other cases the converter methods provides a "null" + // value. + // a default value can only occur if this method is made public. + value = rawValue != null || propNode.GetOptions().IsCompositeProperty() ? rawValue : string.Empty; + break; + } + } + return value; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaParser.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaParser.cs index 7f0d3e36d..7698b756a 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaParser.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPMetaParser.cs @@ -1,345 +1,345 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using System.IO; -using System.Xml; -using Com.Adobe.Xmp.Options; -using Sharpen; -using StringReader = Sharpen.StringReader; - -namespace Com.Adobe.Xmp.Impl -{ - /// - /// This class replaces the ExpatAdapter.cpp and does the - /// XML-parsing and fixes the prefix. - /// - /// - /// This class replaces the ExpatAdapter.cpp and does the - /// XML-parsing and fixes the prefix. After the parsing several normalisations - /// are applied to the XMPTree. - /// - /// 01.02.2006 - public class XMPMetaParser - { - private static readonly object XmpRdf = new object(); - - /// Hidden constructor, initialises the SAX parser handler. - private XMPMetaParser() - { - } - - // EMPTY - /// - /// Parses the input source into an XMP metadata object, including - /// de-aliasing and normalisation. - /// - /// - /// the input can be an InputStream, a String or - /// a byte buffer containing the XMP packet. - /// - /// the parse options - /// Returns the resulting XMP metadata object - /// Thrown if parsing or normalisation fails. - public static XMPMeta Parse(object input, ParseOptions options) - { - ParameterAsserts.AssertNotNull(input); - options = options != null ? options : new ParseOptions(); - XmlDocument document = ParseXml(input, options); - bool xmpmetaRequired = options.GetRequireXMPMeta(); - object[] result = new object[3]; - result = FindRootNode(document, xmpmetaRequired, result); - if (result != null && result[1] == XmpRdf) - { - XMPMetaImpl xmp = ParseRDF.Parse((XmlNode)result[0]); - xmp.SetPacketHeader((string)result[2]); - // Check if the XMP object shall be normalized - if (!options.GetOmitNormalization()) - { - return XMPNormalizer.Process(xmp, options); - } - else - { - return xmp; - } - } - else - { - // no appropriate root node found, return empty metadata object - return new XMPMetaImpl(); - } - } - - /// Parses the raw XML metadata packet considering the parsing options. - /// - /// Parses the raw XML metadata packet considering the parsing options. - /// Latin-1/ISO-8859-1 can be accepted when the input is a byte stream - /// (some old toolkits versions such packets). The stream is - /// then wrapped in another stream that converts Latin-1 to UTF-8. - ///

- /// If control characters shall be fixed, a reader is used that fixes the chars to spaces - /// (if the input is a byte stream is has to be read as character stream). - ///

- /// Both options reduce the performance of the parser. - /// - /// - /// the input can be an InputStream, a String or - /// a byte buffer containing the XMP packet. - /// - /// the parsing options - /// Returns the parsed XML document or an exception. - /// Thrown if the parsing fails for different reasons - private static XmlDocument ParseXml(object input, ParseOptions options) - { - if (input is InputStream) - { - return ParseXmlFromInputStream((InputStream)input, options); - } - else - { - if (input is sbyte[]) - { - return ParseXmlFromBytebuffer(new ByteBuffer((sbyte[])input), options); - } - else - { - return ParseXmlFromString((string)input, options); - } - } - } - - ///

- /// Parses XML from an - /// - /// , - /// fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. - /// - /// an InputStream - /// the parsing options - /// Returns an XML DOM-Document. - /// Thrown when the parsing fails. - private static XmlDocument ParseXmlFromInputStream(InputStream stream, ParseOptions options) - { - if (!options.GetAcceptLatin1() && !options.GetFixControlChars()) - { - return ParseInputSource(new InputSource(stream)); - } - else - { - // load stream into bytebuffer - try - { - ByteBuffer buffer = new ByteBuffer(stream); - return ParseXmlFromBytebuffer(buffer, options); - } - catch (IOException e) - { - throw new XMPException("Error reading the XML-file", XMPErrorConstants.Badstream, e); - } - } - } - - /// - /// Parses XML from a byte buffer, - /// fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. - /// - /// a byte buffer containing the XMP packet - /// the parsing options - /// Returns an XML DOM-Document. - /// Thrown when the parsing fails. - private static XmlDocument ParseXmlFromBytebuffer(ByteBuffer buffer, ParseOptions options) - { - InputSource source = new InputSource(buffer.GetByteStream()); - try - { - return ParseInputSource(source); - } - catch (XMPException e) - { - if (e.GetErrorCode() == XMPErrorConstants.Badxml || e.GetErrorCode() == XMPErrorConstants.Badstream) - { - if (options.GetAcceptLatin1()) - { - buffer = Latin1Converter.Convert(buffer); - } - if (options.GetFixControlChars()) - { - try - { - string encoding = buffer.GetEncoding(); - StreamReader fixReader = new FixASCIIControlsReader(new InputStreamReader(buffer.GetByteStream(), encoding)); - return ParseInputSource(new InputSource(fixReader)); - } - catch (UnsupportedEncodingException) - { - // can normally not happen as the encoding is provided by a util function - throw new XMPException("Unsupported Encoding", XMPErrorConstants.Internalfailure, e); - } - } - source = new InputSource(buffer.GetByteStream()); - return ParseInputSource(source); - } - else - { - throw; - } - } - } - - /// - /// Parses XML from a - /// - /// , - /// fixing the illegal control character optionally. - /// - /// a String containing the XMP packet - /// the parsing options - /// Returns an XML DOM-Document. - /// Thrown when the parsing fails. - private static XmlDocument ParseXmlFromString(string input, ParseOptions options) - { - InputSource source = new InputSource(new StringReader(input)); - try - { - return ParseInputSource(source); - } - catch (XMPException e) - { - if (e.GetErrorCode() == XMPErrorConstants.Badxml && options.GetFixControlChars()) - { - source = new InputSource(new FixASCIIControlsReader(new StringReader(input))); - return ParseInputSource(source); - } - else - { - throw; - } - } - } - - /// Runs the XML-Parser. - /// an InputSource - /// Returns an XML DOM-Document. - /// Wraps parsing and I/O-exceptions into an XMPException. - private static XmlDocument ParseInputSource(InputSource source) - { - try - { - XmlDocument doc = new XmlDocument(); - doc.Load(source.GetStream()); - - return doc; - } - catch (XmlException e) - { - throw new XMPException("XML parsing failure", XMPErrorConstants.Badxml, e); - } - catch (IOException e) - { - throw new XMPException("Error reading the XML-file", XMPErrorConstants.Badstream, e); - } - catch (Exception e) - { - throw new XMPException("XML Parser not correctly configured", XMPErrorConstants.Unknown, e); - } - } - - /// Find the XML node that is the root of the XMP data tree. - /// - /// Find the XML node that is the root of the XMP data tree. Generally this - /// will be an outer node, but it could be anywhere if a general XML document - /// is parsed (e.g. SVG). The XML parser counted all rdf:RDF and - /// pxmp:XMP_Packet nodes, and kept a pointer to the last one. If there is - /// more than one possible root use PickBestRoot to choose among them. - ///

- /// If there is a root node, try to extract the version of the previous XMP - /// toolkit. - ///

- /// Pick the first x:xmpmeta among multiple root candidates. If there aren't - /// any, pick the first bare rdf:RDF if that is allowed. The returned root is - /// the rdf:RDF child if an x:xmpmeta element was chosen. The search is - /// breadth first, so a higher level candiate is chosen over a lower level - /// one that was textually earlier in the serialized XML. - /// - /// the root of the xml document - /// - /// flag if the xmpmeta-tag is still required, might be set - /// initially to true, if the parse option "REQUIRE_XMP_META" is set - /// - /// The result array that is filled during the recursive process. - /// - /// Returns an array that contains the result or null. - /// The array contains: - ///

    - ///
  • [0] - the rdf:RDF-node - ///
  • [1] - an object that is either XMP_RDF or XMP_PLAIN (the latter is decrecated) - ///
  • [2] - the body text of the xpacket-instruction. - ///
- /// - private static object[] FindRootNode(XmlNode root, bool xmpmetaRequired, object[] result) - { - // Look among this parent's content for x:xapmeta or x:xmpmeta. - // The recursion for x:xmpmeta is broader than the strictly defined choice, - // but gives us smaller code. - XmlNodeList children = root.ChildNodes; - for (int i = 0; i < children.Count; i++) - { - root = children.Item(i); - if (XmlNodeType.ProcessingInstruction == root.NodeType && XMPConstConstants.XmpPi.Equals(((XmlProcessingInstruction)root).Target)) - { - // Store the processing instructions content - if (result != null) - { - result[2] = ((XmlProcessingInstruction)root).Data; - } - } - else - { - if (XmlNodeType.Text != root.NodeType && XmlNodeType.ProcessingInstruction != root.NodeType) - { - string rootNS = root.NamespaceURI; - string rootLocal = root.LocalName; - if ((XMPConstConstants.TagXmpmeta.Equals(rootLocal) || XMPConstConstants.TagXapmeta.Equals(rootLocal)) && XMPConstConstants.NsX.Equals(rootNS)) - { - // by not passing the RequireXMPMeta-option, the rdf-Node will be valid - return FindRootNode(root, false, result); - } - else - { - if (!xmpmetaRequired && "RDF".Equals(rootLocal) && XMPConstConstants.NsRdf.Equals(rootNS)) - { - if (result != null) - { - result[0] = root; - result[1] = XmpRdf; - } - return result; - } - else - { - // continue searching - object[] newResult = FindRootNode(root, xmpmetaRequired, result); - if (newResult != null) - { - return newResult; - } - else - { - continue; - } - } - } - } - } - } - // no appropriate node has been found - return null; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using System.IO; +using System.Xml; +using Com.Adobe.Xmp.Options; +using Sharpen; +using StringReader = Sharpen.StringReader; + +namespace Com.Adobe.Xmp.Impl +{ + /// + /// This class replaces the ExpatAdapter.cpp and does the + /// XML-parsing and fixes the prefix. + /// + /// + /// This class replaces the ExpatAdapter.cpp and does the + /// XML-parsing and fixes the prefix. After the parsing several normalisations + /// are applied to the XMPTree. + /// + /// 01.02.2006 + public class XMPMetaParser + { + private static readonly object XmpRdf = new object(); + + /// Hidden constructor, initialises the SAX parser handler. + private XMPMetaParser() + { + } + + // EMPTY + /// + /// Parses the input source into an XMP metadata object, including + /// de-aliasing and normalisation. + /// + /// + /// the input can be an InputStream, a String or + /// a byte buffer containing the XMP packet. + /// + /// the parse options + /// Returns the resulting XMP metadata object + /// Thrown if parsing or normalisation fails. + public static XMPMeta Parse(object input, ParseOptions options) + { + ParameterAsserts.AssertNotNull(input); + options = options != null ? options : new ParseOptions(); + XmlDocument document = ParseXml(input, options); + bool xmpmetaRequired = options.GetRequireXMPMeta(); + object[] result = new object[3]; + result = FindRootNode(document, xmpmetaRequired, result); + if (result != null && result[1] == XmpRdf) + { + XMPMetaImpl xmp = ParseRDF.Parse((XmlNode)result[0]); + xmp.SetPacketHeader((string)result[2]); + // Check if the XMP object shall be normalized + if (!options.GetOmitNormalization()) + { + return XMPNormalizer.Process(xmp, options); + } + else + { + return xmp; + } + } + else + { + // no appropriate root node found, return empty metadata object + return new XMPMetaImpl(); + } + } + + /// Parses the raw XML metadata packet considering the parsing options. + /// + /// Parses the raw XML metadata packet considering the parsing options. + /// Latin-1/ISO-8859-1 can be accepted when the input is a byte stream + /// (some old toolkits versions such packets). The stream is + /// then wrapped in another stream that converts Latin-1 to UTF-8. + ///

+ /// If control characters shall be fixed, a reader is used that fixes the chars to spaces + /// (if the input is a byte stream is has to be read as character stream). + ///

+ /// Both options reduce the performance of the parser. + /// + /// + /// the input can be an InputStream, a String or + /// a byte buffer containing the XMP packet. + /// + /// the parsing options + /// Returns the parsed XML document or an exception. + /// Thrown if the parsing fails for different reasons + private static XmlDocument ParseXml(object input, ParseOptions options) + { + if (input is InputStream) + { + return ParseXmlFromInputStream((InputStream)input, options); + } + else + { + if (input is sbyte[]) + { + return ParseXmlFromBytebuffer(new ByteBuffer((sbyte[])input), options); + } + else + { + return ParseXmlFromString((string)input, options); + } + } + } + + ///

+ /// Parses XML from an + /// + /// , + /// fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. + /// + /// an InputStream + /// the parsing options + /// Returns an XML DOM-Document. + /// Thrown when the parsing fails. + private static XmlDocument ParseXmlFromInputStream(InputStream stream, ParseOptions options) + { + if (!options.GetAcceptLatin1() && !options.GetFixControlChars()) + { + return ParseInputSource(new InputSource(stream)); + } + else + { + // load stream into bytebuffer + try + { + ByteBuffer buffer = new ByteBuffer(stream); + return ParseXmlFromBytebuffer(buffer, options); + } + catch (IOException e) + { + throw new XMPException("Error reading the XML-file", XMPErrorConstants.Badstream, e); + } + } + } + + /// + /// Parses XML from a byte buffer, + /// fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. + /// + /// a byte buffer containing the XMP packet + /// the parsing options + /// Returns an XML DOM-Document. + /// Thrown when the parsing fails. + private static XmlDocument ParseXmlFromBytebuffer(ByteBuffer buffer, ParseOptions options) + { + InputSource source = new InputSource(buffer.GetByteStream()); + try + { + return ParseInputSource(source); + } + catch (XMPException e) + { + if (e.GetErrorCode() == XMPErrorConstants.Badxml || e.GetErrorCode() == XMPErrorConstants.Badstream) + { + if (options.GetAcceptLatin1()) + { + buffer = Latin1Converter.Convert(buffer); + } + if (options.GetFixControlChars()) + { + try + { + string encoding = buffer.GetEncoding(); + StreamReader fixReader = new FixASCIIControlsReader(new InputStreamReader(buffer.GetByteStream(), encoding)); + return ParseInputSource(new InputSource(fixReader)); + } + catch (UnsupportedEncodingException) + { + // can normally not happen as the encoding is provided by a util function + throw new XMPException("Unsupported Encoding", XMPErrorConstants.Internalfailure, e); + } + } + source = new InputSource(buffer.GetByteStream()); + return ParseInputSource(source); + } + else + { + throw; + } + } + } + + /// + /// Parses XML from a + /// + /// , + /// fixing the illegal control character optionally. + /// + /// a String containing the XMP packet + /// the parsing options + /// Returns an XML DOM-Document. + /// Thrown when the parsing fails. + private static XmlDocument ParseXmlFromString(string input, ParseOptions options) + { + InputSource source = new InputSource(new StringReader(input)); + try + { + return ParseInputSource(source); + } + catch (XMPException e) + { + if (e.GetErrorCode() == XMPErrorConstants.Badxml && options.GetFixControlChars()) + { + source = new InputSource(new FixASCIIControlsReader(new StringReader(input))); + return ParseInputSource(source); + } + else + { + throw; + } + } + } + + /// Runs the XML-Parser. + /// an InputSource + /// Returns an XML DOM-Document. + /// Wraps parsing and I/O-exceptions into an XMPException. + private static XmlDocument ParseInputSource(InputSource source) + { + try + { + XmlDocument doc = new XmlDocument(); + doc.Load(source.GetStream()); + + return doc; + } + catch (XmlException e) + { + throw new XMPException("XML parsing failure", XMPErrorConstants.Badxml, e); + } + catch (IOException e) + { + throw new XMPException("Error reading the XML-file", XMPErrorConstants.Badstream, e); + } + catch (Exception e) + { + throw new XMPException("XML Parser not correctly configured", XMPErrorConstants.Unknown, e); + } + } + + /// Find the XML node that is the root of the XMP data tree. + /// + /// Find the XML node that is the root of the XMP data tree. Generally this + /// will be an outer node, but it could be anywhere if a general XML document + /// is parsed (e.g. SVG). The XML parser counted all rdf:RDF and + /// pxmp:XMP_Packet nodes, and kept a pointer to the last one. If there is + /// more than one possible root use PickBestRoot to choose among them. + ///

+ /// If there is a root node, try to extract the version of the previous XMP + /// toolkit. + ///

+ /// Pick the first x:xmpmeta among multiple root candidates. If there aren't + /// any, pick the first bare rdf:RDF if that is allowed. The returned root is + /// the rdf:RDF child if an x:xmpmeta element was chosen. The search is + /// breadth first, so a higher level candiate is chosen over a lower level + /// one that was textually earlier in the serialized XML. + /// + /// the root of the xml document + /// + /// flag if the xmpmeta-tag is still required, might be set + /// initially to true, if the parse option "REQUIRE_XMP_META" is set + /// + /// The result array that is filled during the recursive process. + /// + /// Returns an array that contains the result or null. + /// The array contains: + ///

    + ///
  • [0] - the rdf:RDF-node + ///
  • [1] - an object that is either XMP_RDF or XMP_PLAIN (the latter is decrecated) + ///
  • [2] - the body text of the xpacket-instruction. + ///
+ /// + private static object[] FindRootNode(XmlNode root, bool xmpmetaRequired, object[] result) + { + // Look among this parent's content for x:xapmeta or x:xmpmeta. + // The recursion for x:xmpmeta is broader than the strictly defined choice, + // but gives us smaller code. + XmlNodeList children = root.ChildNodes; + for (int i = 0; i < children.Count; i++) + { + root = children.Item(i); + if (XmlNodeType.ProcessingInstruction == root.NodeType && XMPConstConstants.XmpPi.Equals(((XmlProcessingInstruction)root).Target)) + { + // Store the processing instructions content + if (result != null) + { + result[2] = ((XmlProcessingInstruction)root).Data; + } + } + else + { + if (XmlNodeType.Text != root.NodeType && XmlNodeType.ProcessingInstruction != root.NodeType) + { + string rootNS = root.NamespaceURI; + string rootLocal = root.LocalName; + if ((XMPConstConstants.TagXmpmeta.Equals(rootLocal) || XMPConstConstants.TagXapmeta.Equals(rootLocal)) && XMPConstConstants.NsX.Equals(rootNS)) + { + // by not passing the RequireXMPMeta-option, the rdf-Node will be valid + return FindRootNode(root, false, result); + } + else + { + if (!xmpmetaRequired && "RDF".Equals(rootLocal) && XMPConstConstants.NsRdf.Equals(rootNS)) + { + if (result != null) + { + result[0] = root; + result[1] = XmpRdf; + } + return result; + } + else + { + // continue searching + object[] newResult = FindRootNode(root, xmpmetaRequired, result); + if (newResult != null) + { + return newResult; + } + else + { + continue; + } + } + } + } + } + } + // no appropriate node has been found + return null; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNode.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNode.cs index b18d6b71e..110691123 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNode.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNode.cs @@ -1,764 +1,764 @@ -//================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using System.Collections; -using System.Diagnostics; -using System.Text; -using Com.Adobe.Xmp.Options; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// - /// A node in the internally XMP tree, which can be a schema node, a property node, an array node, - /// an array item, a struct node or a qualifier node (without '?'). - /// - /// - /// A node in the internally XMP tree, which can be a schema node, a property node, an array node, - /// an array item, a struct node or a qualifier node (without '?'). - /// Possible improvements: - /// 1. The kind Node of node might be better represented by a class-hierarchy of different nodes. - /// 2. The array type should be an enum - /// 3. isImplicitNode should be removed completely and replaced by return values of fi. - /// 4. hasLanguage, hasType should be automatically maintained by XMPNode - /// - /// 21.02.2006 - public class XMPNode : IComparable - { - /// name of the node, contains different information depending of the node kind - private string name; - - /// value of the node, contains different information depending of the node kind - private string value; - - /// link to the parent node - private XMPNode parent; - - /// list of child nodes, lazy initialized - private IList children = null; - - /// list of qualifier of the node, lazy initialized - private IList qualifier = null; - - /// options describing the kind of the node - private PropertyOptions options = null; - - /// flag if the node is implicitly created - private bool @implicit; - - /// flag if the node has aliases - private bool hasAliases; - - /// flag if the node is an alias - private bool alias; - - /// flag if the node has an "rdf:value" child node. - private bool hasValueChild; - - /// Creates an XMPNode with initial values. - /// the name of the node - /// the value of the node - /// the options of the node - public XMPNode(string name, string value, PropertyOptions options) - { - // internal processing options - this.name = name; - this.value = value; - this.options = options; - } - - /// Constructor for the node without value. - /// the name of the node - /// the options of the node - public XMPNode(string name, PropertyOptions options) - : this(name, null, options) - { - } - - /// Resets the node. - public virtual void Clear() - { - options = null; - name = null; - value = null; - children = null; - qualifier = null; - } - - /// Returns the parent node. - public virtual XMPNode GetParent() - { - return parent; - } - - /// an index [1..size] - /// Returns the child with the requested index. - public virtual XMPNode GetChild(int index) - { - return (XMPNode)GetChildren()[index - 1]; - } - - /// Adds a node as child to this node. - /// an XMPNode - /// - public virtual void AddChild(XMPNode node) - { - // check for duplicate properties - AssertChildNotExisting(node.GetName()); - node.SetParent(this); - GetChildren().Add(node); - } - - /// Adds a node as child to this node. - /// - /// the index of the node before which the new one is inserted. - /// Note: The node children are indexed from [1..size]! - /// An index of size + 1 appends a node. - /// - /// an XMPNode - /// - public virtual void AddChild(int index, XMPNode node) - { - AssertChildNotExisting(node.GetName()); - node.SetParent(this); - GetChildren().Add(index - 1, node); - } - - /// Replaces a node with another one. - /// - /// the index of the node that will be replaced. - /// Note: The node children are indexed from [1..size]! - /// - /// the replacement XMPNode - public virtual void ReplaceChild(int index, XMPNode node) - { - node.SetParent(this); - GetChildren().Set(index - 1, node); - } - - /// Removes a child at the requested index. - /// the index to remove [1..size] - public virtual void RemoveChild(int itemIndex) - { - GetChildren().Remove(itemIndex - 1); - CleanupChildren(); - } - - /// Removes a child node. - /// - /// Removes a child node. - /// If its a schema node and doesn't have any children anymore, its deleted. - /// - /// the child node to delete. - public virtual void RemoveChild(XMPNode node) - { - GetChildren().Remove(node); - CleanupChildren(); - } - - /// - /// Removes the children list if this node has no children anymore; - /// checks if the provided node is a schema node and doesn't have any children anymore, - /// its deleted. - /// - protected internal virtual void CleanupChildren() - { - if (children.IsEmpty()) - { - children = null; - } - } - - /// Removes all children from the node. - public virtual void RemoveChildren() - { - children = null; - } - - /// Returns the number of children without neccessarily creating a list. - public virtual int GetChildrenLength() - { - return children != null ? children.Count : 0; - } - - /// child node name to look for - /// Returns an XMPNode if node has been found, null otherwise. - public virtual XMPNode FindChildByName(string expr) - { - return Find(GetChildren(), expr); - } - - /// an index [1..size] - /// Returns the qualifier with the requested index. - public virtual XMPNode GetQualifier(int index) - { - return (XMPNode)GetQualifier()[index - 1]; - } - - /// Returns the number of qualifier without neccessarily creating a list. - public virtual int GetQualifierLength() - { - return qualifier != null ? qualifier.Count : 0; - } - - /// Appends a qualifier to the qualifier list and sets respective options. - /// a qualifier node. - /// - public virtual void AddQualifier(XMPNode qualNode) - { - AssertQualifierNotExisting(qualNode.GetName()); - qualNode.SetParent(this); - qualNode.GetOptions().SetQualifier(true); - GetOptions().SetHasQualifiers(true); - // contraints - if (qualNode.IsLanguageNode()) - { - // "xml:lang" is always first and the option "hasLanguage" is set - options.SetHasLanguage(true); - GetQualifier().Add(0, qualNode); - } - else - { - if (qualNode.IsTypeNode()) - { - // "rdf:type" must be first or second after "xml:lang" and the option "hasType" is set - options.SetHasType(true); - GetQualifier().Add(!options.GetHasLanguage() ? 0 : 1, qualNode); - } - else - { - // other qualifiers are appended - GetQualifier().Add(qualNode); - } - } - } - - /// Removes one qualifier node and fixes the options. - /// qualifier to remove - public virtual void RemoveQualifier(XMPNode qualNode) - { - PropertyOptions opts = GetOptions(); - if (qualNode.IsLanguageNode()) - { - // if "xml:lang" is removed, remove hasLanguage-flag too - opts.SetHasLanguage(false); - } - else - { - if (qualNode.IsTypeNode()) - { - // if "rdf:type" is removed, remove hasType-flag too - opts.SetHasType(false); - } - } - GetQualifier().Remove(qualNode); - if (qualifier.IsEmpty()) - { - opts.SetHasQualifiers(false); - qualifier = null; - } - } - - /// Removes all qualifiers from the node and sets the options appropriate. - public virtual void RemoveQualifiers() - { - PropertyOptions opts = GetOptions(); - // clear qualifier related options - opts.SetHasQualifiers(false); - opts.SetHasLanguage(false); - opts.SetHasType(false); - qualifier = null; - } - - /// qualifier node name to look for - /// - /// Returns a qualifier XMPNode if node has been found, - /// null otherwise. - /// - public virtual XMPNode FindQualifierByName(string expr) - { - return Find(qualifier, expr); - } - - /// Returns whether the node has children. - public virtual bool HasChildren() - { - return children != null && children.Count > 0; - } - - /// - /// Returns an iterator for the children. - /// Note: take care to use it.remove(), as the flag are not adjusted in that case. - /// - public virtual Iterator IterateChildren() - { - if (children != null) - { - return GetChildren().Iterator(); - } - else - { - return Collections.EmptyList().ListIterator(); - } - } - - /// Returns whether the node has qualifier attached. - public virtual bool HasQualifier() - { - return qualifier != null && qualifier.Count > 0; - } - - /// - /// Returns an iterator for the qualifier. - /// Note: take care to use it.remove(), as the flag are not adjusted in that case. - /// - public virtual Iterator IterateQualifier() - { - if (qualifier != null) - { - Iterator it = GetQualifier().Iterator(); - return new _Iterator_391(it); - } - else - { - return Collections.EmptyList().Iterator(); - } - } - - private sealed class _Iterator_391 : Iterator - { - public _Iterator_391(Iterator it) - { - this.it = it; - } - - public bool HasNext() - { - return it.HasNext(); - } - - public object Next() - { - return it.Next(); - } - - public void Remove() - { - throw new NotSupportedException("remove() is not allowed due to the internal contraints"); - } - - private readonly Iterator it; - } - - /// Performs a deep clone of the node and the complete subtree. - /// - public virtual object Clone() - { - PropertyOptions newOptions; - try - { - newOptions = new PropertyOptions(GetOptions().GetOptions()); - } - catch (XMPException) - { - // cannot happen - newOptions = new PropertyOptions(); - } - XMPNode newNode = new XMPNode(name, value, newOptions); - CloneSubtree(newNode); - return newNode; - } - - /// - /// Performs a deep clone of the complete subtree (children and - /// qualifier )into and add it to the destination node. - /// - /// the node to add the cloned subtree - public virtual void CloneSubtree(XMPNode destination) - { - try - { - for (Iterator it = IterateChildren(); it.HasNext(); ) - { - XMPNode child = (XMPNode)it.Next(); - destination.AddChild((XMPNode)child.Clone()); - } - for (Iterator it_1 = IterateQualifier(); it_1.HasNext(); ) - { - XMPNode qualifier = (XMPNode)it_1.Next(); - destination.AddQualifier((XMPNode)qualifier.Clone()); - } - } - catch (XMPException) - { - // cannot happen (duplicate childs/quals do not exist in this node) - Debug.Assert(false); - } - } - - /// Renders this node and the tree unter this node in a human readable form. - /// Flag is qualifier and child nodes shall be rendered too - /// Returns a multiline string containing the dump. - public virtual string DumpNode(bool recursive) - { - StringBuilder result = new StringBuilder(512); - this.DumpNode(result, recursive, 0, 0); - return result.ToString(); - } - - /// - public virtual int CompareTo(object xmpNode) - { - if (GetOptions().IsSchemaNode()) - { - return string.CompareOrdinal(this.value, ((XMPNode)xmpNode).GetValue()); - } - else - { - return string.CompareOrdinal(this.name, ((XMPNode)xmpNode).GetName()); - } - } - - /// Returns the name. - public virtual string GetName() - { - return name; - } - - /// The name to set. - public virtual void SetName(string name) - { - this.name = name; - } - - /// Returns the value. - public virtual string GetValue() - { - return value; - } - - /// The value to set. - public virtual void SetValue(string value) - { - this.value = value; - } - - /// Returns the options. - public virtual PropertyOptions GetOptions() - { - if (options == null) - { - options = new PropertyOptions(); - } - return options; - } - - /// Updates the options of the node. - /// the options to set. - public virtual void SetOptions(PropertyOptions options) - { - this.options = options; - } - - /// Returns the implicit flag - public virtual bool IsImplicit() - { - return @implicit; - } - - /// Sets the implicit node flag - public virtual void SetImplicit(bool @implicit) - { - this.@implicit = @implicit; - } - - /// Returns if the node contains aliases (applies only to schema nodes) - public virtual bool GetHasAliases() - { - return hasAliases; - } - - /// sets the flag that the node contains aliases - public virtual void SetHasAliases(bool hasAliases) - { - this.hasAliases = hasAliases; - } - - /// Returns if the node contains aliases (applies only to schema nodes) - public virtual bool IsAlias() - { - return alias; - } - - /// sets the flag that the node is an alias - public virtual void SetAlias(bool alias) - { - this.alias = alias; - } - - /// the hasValueChild - public virtual bool GetHasValueChild() - { - return hasValueChild; - } - - /// the hasValueChild to set - public virtual void SetHasValueChild(bool hasValueChild) - { - this.hasValueChild = hasValueChild; - } - - /// - /// Sorts the complete datamodel according to the following rules: - ///
    - ///
  • Nodes at one level are sorted by name, that is prefix + local name - ///
  • Starting at the root node the children and qualifier are sorted recursively, - /// which the following exceptions. - ///
- /// - /// Sorts the complete datamodel according to the following rules: - ///
    - ///
  • Nodes at one level are sorted by name, that is prefix + local name - ///
  • Starting at the root node the children and qualifier are sorted recursively, - /// which the following exceptions. - ///
  • Sorting will not be used for arrays. - ///
  • Within qualifier "xml:lang" and/or "rdf:type" stay at the top in that order, - /// all others are sorted. - ///
- ///
- public virtual void Sort() - { - // sort qualifier - if (HasQualifier()) - { - XMPNode[] quals = (XMPNode[])Collections.ToArray(GetQualifier(), new XMPNode[GetQualifierLength()]); - int sortFrom = 0; - while (quals.Length > sortFrom && (XMPConstConstants.XmlLang.Equals(quals[sortFrom].GetName()) || "rdf:type".Equals(quals[sortFrom].GetName()))) - { - quals[sortFrom].Sort(); - sortFrom++; - } - Arrays.Sort(quals, sortFrom, quals.Length); - ListIterator it = qualifier.ListIterator(); - for (int j = 0; j < quals.Length; j++) - { - it.Next(); - it.Set(quals[j]); - quals[j].Sort(); - } - } - // sort children - if (HasChildren()) - { - if (!GetOptions().IsArray()) - { - children.Sort(); - } - for (Iterator it = IterateChildren(); it.HasNext(); ) - { - ((XMPNode)it.Next()).Sort(); - } - } - } - - //------------------------------------------------------------------------------ private methods - /// Dumps this node and its qualifier and children recursively. - /// - /// Dumps this node and its qualifier and children recursively. - /// Note: It creats empty options on every node. - /// - /// the buffer to append the dump. - /// Flag is qualifier and child nodes shall be rendered too - /// the current indent level. - /// the index within the parent node (important for arrays) - private void DumpNode(StringBuilder result, bool recursive, int indent, int index) - { - // write indent - for (int i = 0; i < indent; i++) - { - result.Append('\t'); - } - // render Node - if (parent != null) - { - if (GetOptions().IsQualifier()) - { - result.Append('?'); - result.Append(name); - } - else - { - if (GetParent().GetOptions().IsArray()) - { - result.Append('['); - result.Append(index); - result.Append(']'); - } - else - { - result.Append(name); - } - } - } - else - { - // applies only to the root node - result.Append("ROOT NODE"); - if (name != null && name.Length > 0) - { - // the "about" attribute - result.Append(" ("); - result.Append(name); - result.Append(')'); - } - } - if (value != null && value.Length > 0) - { - result.Append(" = \""); - result.Append(value); - result.Append('"'); - } - // render options if at least one is set - if (GetOptions().ContainsOneOf(unchecked((int)(0xffffffff)))) - { - result.Append("\t("); - result.Append(GetOptions().ToString()); - result.Append(" : "); - result.Append(GetOptions().GetOptionsString()); - result.Append(')'); - } - result.Append('\n'); - // render qualifier - if (recursive && HasQualifier()) - { - XMPNode[] quals = (XMPNode[])Collections.ToArray(GetQualifier(), new XMPNode[GetQualifierLength()]); - int i_1 = 0; - while (quals.Length > i_1 && (XMPConstConstants.XmlLang.Equals(quals[i_1].GetName()) || "rdf:type".Equals(quals[i_1].GetName()))) - { - i_1++; - } - Arrays.Sort(quals, i_1, quals.Length); - for (i_1 = 0; i_1 < quals.Length; i_1++) - { - XMPNode qualifier = quals[i_1]; - qualifier.DumpNode(result, recursive, indent + 2, i_1 + 1); - } - } - // render children - if (recursive && HasChildren()) - { - XMPNode[] children = (XMPNode[])Collections.ToArray(GetChildren(), new XMPNode[GetChildrenLength()]); - if (!GetOptions().IsArray()) - { - Arrays.Sort(children); - } - for (int i_1 = 0; i_1 < children.Length; i_1++) - { - XMPNode child = children[i_1]; - child.DumpNode(result, recursive, indent + 1, i_1 + 1); - } - } - } - - /// Returns whether this node is a language qualifier. - private bool IsLanguageNode() - { - return XMPConstConstants.XmlLang.Equals(name); - } - - /// Returns whether this node is a type qualifier. - private bool IsTypeNode() - { - return "rdf:type".Equals(name); - } - - /// - /// Note: This method should always be called when accessing 'children' to be sure - /// that its initialized. - /// - /// Returns list of children that is lazy initialized. - private IList GetChildren() - { - if (children == null) - { - children = new ArrayList(0); - } - return children; - } - - /// Returns a read-only copy of child nodes list. - public virtual IList GetUnmodifiableChildren() - { - return Collections.UnmodifiableList(new ArrayList(GetChildren())); - } - - /// Returns list of qualifier that is lazy initialized. - private IList GetQualifier() - { - if (qualifier == null) - { - qualifier = new ArrayList(0); - } - return qualifier; - } - - /// - /// Sets the parent node, this is solely done by addChild(...) - /// and addQualifier(). - /// - /// Sets the parent node. - protected internal virtual void SetParent(XMPNode parent) - { - this.parent = parent; - } - - /// Internal find. - /// the list to search in - /// the search expression - /// Returns the found node or nulls. - private XMPNode Find(IList list, string expr) - { - if (list != null) - { - for (Iterator it = list.Iterator(); it.HasNext(); ) - { - XMPNode child = (XMPNode)it.Next(); - if (child.GetName().Equals(expr)) - { - return child; - } - } - } - return null; - } - - /// Checks that a node name is not existing on the same level, except for array items. - /// the node name to check - /// Thrown if a node with the same name is existing. - private void AssertChildNotExisting(string childName) - { - if (!XMPConstConstants.ArrayItemName.Equals(childName) && FindChildByName(childName) != null) - { - throw new XMPException("Duplicate property or field node '" + childName + "'", XMPErrorConstants.Badxmp); - } - } - - /// Checks that a qualifier name is not existing on the same level. - /// the new qualifier name - /// Thrown if a node with the same name is existing. - private void AssertQualifierNotExisting(string qualifierName) - { - if (!XMPConstConstants.ArrayItemName.Equals(qualifierName) && FindQualifierByName(qualifierName) != null) - { - throw new XMPException("Duplicate '" + qualifierName + "' qualifier", XMPErrorConstants.Badxmp); - } - } - } -} +//================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using System.Collections; +using System.Diagnostics; +using System.Text; +using Com.Adobe.Xmp.Options; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// + /// A node in the internally XMP tree, which can be a schema node, a property node, an array node, + /// an array item, a struct node or a qualifier node (without '?'). + /// + /// + /// A node in the internally XMP tree, which can be a schema node, a property node, an array node, + /// an array item, a struct node or a qualifier node (without '?'). + /// Possible improvements: + /// 1. The kind Node of node might be better represented by a class-hierarchy of different nodes. + /// 2. The array type should be an enum + /// 3. isImplicitNode should be removed completely and replaced by return values of fi. + /// 4. hasLanguage, hasType should be automatically maintained by XMPNode + /// + /// 21.02.2006 + public class XMPNode : IComparable + { + /// name of the node, contains different information depending of the node kind + private string name; + + /// value of the node, contains different information depending of the node kind + private string value; + + /// link to the parent node + private XMPNode parent; + + /// list of child nodes, lazy initialized + private IList children = null; + + /// list of qualifier of the node, lazy initialized + private IList qualifier = null; + + /// options describing the kind of the node + private PropertyOptions options = null; + + /// flag if the node is implicitly created + private bool @implicit; + + /// flag if the node has aliases + private bool hasAliases; + + /// flag if the node is an alias + private bool alias; + + /// flag if the node has an "rdf:value" child node. + private bool hasValueChild; + + /// Creates an XMPNode with initial values. + /// the name of the node + /// the value of the node + /// the options of the node + public XMPNode(string name, string value, PropertyOptions options) + { + // internal processing options + this.name = name; + this.value = value; + this.options = options; + } + + /// Constructor for the node without value. + /// the name of the node + /// the options of the node + public XMPNode(string name, PropertyOptions options) + : this(name, null, options) + { + } + + /// Resets the node. + public virtual void Clear() + { + options = null; + name = null; + value = null; + children = null; + qualifier = null; + } + + /// Returns the parent node. + public virtual XMPNode GetParent() + { + return parent; + } + + /// an index [1..size] + /// Returns the child with the requested index. + public virtual XMPNode GetChild(int index) + { + return (XMPNode)GetChildren()[index - 1]; + } + + /// Adds a node as child to this node. + /// an XMPNode + /// + public virtual void AddChild(XMPNode node) + { + // check for duplicate properties + AssertChildNotExisting(node.GetName()); + node.SetParent(this); + GetChildren().Add(node); + } + + /// Adds a node as child to this node. + /// + /// the index of the node before which the new one is inserted. + /// Note: The node children are indexed from [1..size]! + /// An index of size + 1 appends a node. + /// + /// an XMPNode + /// + public virtual void AddChild(int index, XMPNode node) + { + AssertChildNotExisting(node.GetName()); + node.SetParent(this); + GetChildren().Add(index - 1, node); + } + + /// Replaces a node with another one. + /// + /// the index of the node that will be replaced. + /// Note: The node children are indexed from [1..size]! + /// + /// the replacement XMPNode + public virtual void ReplaceChild(int index, XMPNode node) + { + node.SetParent(this); + GetChildren().Set(index - 1, node); + } + + /// Removes a child at the requested index. + /// the index to remove [1..size] + public virtual void RemoveChild(int itemIndex) + { + GetChildren().Remove(itemIndex - 1); + CleanupChildren(); + } + + /// Removes a child node. + /// + /// Removes a child node. + /// If its a schema node and doesn't have any children anymore, its deleted. + /// + /// the child node to delete. + public virtual void RemoveChild(XMPNode node) + { + GetChildren().Remove(node); + CleanupChildren(); + } + + /// + /// Removes the children list if this node has no children anymore; + /// checks if the provided node is a schema node and doesn't have any children anymore, + /// its deleted. + /// + protected internal virtual void CleanupChildren() + { + if (children.IsEmpty()) + { + children = null; + } + } + + /// Removes all children from the node. + public virtual void RemoveChildren() + { + children = null; + } + + /// Returns the number of children without neccessarily creating a list. + public virtual int GetChildrenLength() + { + return children != null ? children.Count : 0; + } + + /// child node name to look for + /// Returns an XMPNode if node has been found, null otherwise. + public virtual XMPNode FindChildByName(string expr) + { + return Find(GetChildren(), expr); + } + + /// an index [1..size] + /// Returns the qualifier with the requested index. + public virtual XMPNode GetQualifier(int index) + { + return (XMPNode)GetQualifier()[index - 1]; + } + + /// Returns the number of qualifier without neccessarily creating a list. + public virtual int GetQualifierLength() + { + return qualifier != null ? qualifier.Count : 0; + } + + /// Appends a qualifier to the qualifier list and sets respective options. + /// a qualifier node. + /// + public virtual void AddQualifier(XMPNode qualNode) + { + AssertQualifierNotExisting(qualNode.GetName()); + qualNode.SetParent(this); + qualNode.GetOptions().SetQualifier(true); + GetOptions().SetHasQualifiers(true); + // contraints + if (qualNode.IsLanguageNode()) + { + // "xml:lang" is always first and the option "hasLanguage" is set + options.SetHasLanguage(true); + GetQualifier().Add(0, qualNode); + } + else + { + if (qualNode.IsTypeNode()) + { + // "rdf:type" must be first or second after "xml:lang" and the option "hasType" is set + options.SetHasType(true); + GetQualifier().Add(!options.GetHasLanguage() ? 0 : 1, qualNode); + } + else + { + // other qualifiers are appended + GetQualifier().Add(qualNode); + } + } + } + + /// Removes one qualifier node and fixes the options. + /// qualifier to remove + public virtual void RemoveQualifier(XMPNode qualNode) + { + PropertyOptions opts = GetOptions(); + if (qualNode.IsLanguageNode()) + { + // if "xml:lang" is removed, remove hasLanguage-flag too + opts.SetHasLanguage(false); + } + else + { + if (qualNode.IsTypeNode()) + { + // if "rdf:type" is removed, remove hasType-flag too + opts.SetHasType(false); + } + } + GetQualifier().Remove(qualNode); + if (qualifier.IsEmpty()) + { + opts.SetHasQualifiers(false); + qualifier = null; + } + } + + /// Removes all qualifiers from the node and sets the options appropriate. + public virtual void RemoveQualifiers() + { + PropertyOptions opts = GetOptions(); + // clear qualifier related options + opts.SetHasQualifiers(false); + opts.SetHasLanguage(false); + opts.SetHasType(false); + qualifier = null; + } + + /// qualifier node name to look for + /// + /// Returns a qualifier XMPNode if node has been found, + /// null otherwise. + /// + public virtual XMPNode FindQualifierByName(string expr) + { + return Find(qualifier, expr); + } + + /// Returns whether the node has children. + public virtual bool HasChildren() + { + return children != null && children.Count > 0; + } + + /// + /// Returns an iterator for the children. + /// Note: take care to use it.remove(), as the flag are not adjusted in that case. + /// + public virtual Iterator IterateChildren() + { + if (children != null) + { + return GetChildren().Iterator(); + } + else + { + return Collections.EmptyList().ListIterator(); + } + } + + /// Returns whether the node has qualifier attached. + public virtual bool HasQualifier() + { + return qualifier != null && qualifier.Count > 0; + } + + /// + /// Returns an iterator for the qualifier. + /// Note: take care to use it.remove(), as the flag are not adjusted in that case. + /// + public virtual Iterator IterateQualifier() + { + if (qualifier != null) + { + Iterator it = GetQualifier().Iterator(); + return new _Iterator_391(it); + } + else + { + return Collections.EmptyList().Iterator(); + } + } + + private sealed class _Iterator_391 : Iterator + { + public _Iterator_391(Iterator it) + { + this.it = it; + } + + public bool HasNext() + { + return it.HasNext(); + } + + public object Next() + { + return it.Next(); + } + + public void Remove() + { + throw new NotSupportedException("remove() is not allowed due to the internal contraints"); + } + + private readonly Iterator it; + } + + /// Performs a deep clone of the node and the complete subtree. + /// + public virtual object Clone() + { + PropertyOptions newOptions; + try + { + newOptions = new PropertyOptions(GetOptions().GetOptions()); + } + catch (XMPException) + { + // cannot happen + newOptions = new PropertyOptions(); + } + XMPNode newNode = new XMPNode(name, value, newOptions); + CloneSubtree(newNode); + return newNode; + } + + /// + /// Performs a deep clone of the complete subtree (children and + /// qualifier )into and add it to the destination node. + /// + /// the node to add the cloned subtree + public virtual void CloneSubtree(XMPNode destination) + { + try + { + for (Iterator it = IterateChildren(); it.HasNext(); ) + { + XMPNode child = (XMPNode)it.Next(); + destination.AddChild((XMPNode)child.Clone()); + } + for (Iterator it_1 = IterateQualifier(); it_1.HasNext(); ) + { + XMPNode qualifier = (XMPNode)it_1.Next(); + destination.AddQualifier((XMPNode)qualifier.Clone()); + } + } + catch (XMPException) + { + // cannot happen (duplicate childs/quals do not exist in this node) + Debug.Assert(false); + } + } + + /// Renders this node and the tree unter this node in a human readable form. + /// Flag is qualifier and child nodes shall be rendered too + /// Returns a multiline string containing the dump. + public virtual string DumpNode(bool recursive) + { + StringBuilder result = new StringBuilder(512); + this.DumpNode(result, recursive, 0, 0); + return result.ToString(); + } + + /// + public virtual int CompareTo(object xmpNode) + { + if (GetOptions().IsSchemaNode()) + { + return string.CompareOrdinal(this.value, ((XMPNode)xmpNode).GetValue()); + } + else + { + return string.CompareOrdinal(this.name, ((XMPNode)xmpNode).GetName()); + } + } + + /// Returns the name. + public virtual string GetName() + { + return name; + } + + /// The name to set. + public virtual void SetName(string name) + { + this.name = name; + } + + /// Returns the value. + public virtual string GetValue() + { + return value; + } + + /// The value to set. + public virtual void SetValue(string value) + { + this.value = value; + } + + /// Returns the options. + public virtual PropertyOptions GetOptions() + { + if (options == null) + { + options = new PropertyOptions(); + } + return options; + } + + /// Updates the options of the node. + /// the options to set. + public virtual void SetOptions(PropertyOptions options) + { + this.options = options; + } + + /// Returns the implicit flag + public virtual bool IsImplicit() + { + return @implicit; + } + + /// Sets the implicit node flag + public virtual void SetImplicit(bool @implicit) + { + this.@implicit = @implicit; + } + + /// Returns if the node contains aliases (applies only to schema nodes) + public virtual bool GetHasAliases() + { + return hasAliases; + } + + /// sets the flag that the node contains aliases + public virtual void SetHasAliases(bool hasAliases) + { + this.hasAliases = hasAliases; + } + + /// Returns if the node contains aliases (applies only to schema nodes) + public virtual bool IsAlias() + { + return alias; + } + + /// sets the flag that the node is an alias + public virtual void SetAlias(bool alias) + { + this.alias = alias; + } + + /// the hasValueChild + public virtual bool GetHasValueChild() + { + return hasValueChild; + } + + /// the hasValueChild to set + public virtual void SetHasValueChild(bool hasValueChild) + { + this.hasValueChild = hasValueChild; + } + + /// + /// Sorts the complete datamodel according to the following rules: + ///
    + ///
  • Nodes at one level are sorted by name, that is prefix + local name + ///
  • Starting at the root node the children and qualifier are sorted recursively, + /// which the following exceptions. + ///
+ /// + /// Sorts the complete datamodel according to the following rules: + ///
    + ///
  • Nodes at one level are sorted by name, that is prefix + local name + ///
  • Starting at the root node the children and qualifier are sorted recursively, + /// which the following exceptions. + ///
  • Sorting will not be used for arrays. + ///
  • Within qualifier "xml:lang" and/or "rdf:type" stay at the top in that order, + /// all others are sorted. + ///
+ ///
+ public virtual void Sort() + { + // sort qualifier + if (HasQualifier()) + { + XMPNode[] quals = (XMPNode[])Collections.ToArray(GetQualifier(), new XMPNode[GetQualifierLength()]); + int sortFrom = 0; + while (quals.Length > sortFrom && (XMPConstConstants.XmlLang.Equals(quals[sortFrom].GetName()) || "rdf:type".Equals(quals[sortFrom].GetName()))) + { + quals[sortFrom].Sort(); + sortFrom++; + } + Arrays.Sort(quals, sortFrom, quals.Length); + ListIterator it = qualifier.ListIterator(); + for (int j = 0; j < quals.Length; j++) + { + it.Next(); + it.Set(quals[j]); + quals[j].Sort(); + } + } + // sort children + if (HasChildren()) + { + if (!GetOptions().IsArray()) + { + children.Sort(); + } + for (Iterator it = IterateChildren(); it.HasNext(); ) + { + ((XMPNode)it.Next()).Sort(); + } + } + } + + //------------------------------------------------------------------------------ private methods + /// Dumps this node and its qualifier and children recursively. + /// + /// Dumps this node and its qualifier and children recursively. + /// Note: It creats empty options on every node. + /// + /// the buffer to append the dump. + /// Flag is qualifier and child nodes shall be rendered too + /// the current indent level. + /// the index within the parent node (important for arrays) + private void DumpNode(StringBuilder result, bool recursive, int indent, int index) + { + // write indent + for (int i = 0; i < indent; i++) + { + result.Append('\t'); + } + // render Node + if (parent != null) + { + if (GetOptions().IsQualifier()) + { + result.Append('?'); + result.Append(name); + } + else + { + if (GetParent().GetOptions().IsArray()) + { + result.Append('['); + result.Append(index); + result.Append(']'); + } + else + { + result.Append(name); + } + } + } + else + { + // applies only to the root node + result.Append("ROOT NODE"); + if (name != null && name.Length > 0) + { + // the "about" attribute + result.Append(" ("); + result.Append(name); + result.Append(')'); + } + } + if (value != null && value.Length > 0) + { + result.Append(" = \""); + result.Append(value); + result.Append('"'); + } + // render options if at least one is set + if (GetOptions().ContainsOneOf(unchecked((int)(0xffffffff)))) + { + result.Append("\t("); + result.Append(GetOptions().ToString()); + result.Append(" : "); + result.Append(GetOptions().GetOptionsString()); + result.Append(')'); + } + result.Append('\n'); + // render qualifier + if (recursive && HasQualifier()) + { + XMPNode[] quals = (XMPNode[])Collections.ToArray(GetQualifier(), new XMPNode[GetQualifierLength()]); + int i_1 = 0; + while (quals.Length > i_1 && (XMPConstConstants.XmlLang.Equals(quals[i_1].GetName()) || "rdf:type".Equals(quals[i_1].GetName()))) + { + i_1++; + } + Arrays.Sort(quals, i_1, quals.Length); + for (i_1 = 0; i_1 < quals.Length; i_1++) + { + XMPNode qualifier = quals[i_1]; + qualifier.DumpNode(result, recursive, indent + 2, i_1 + 1); + } + } + // render children + if (recursive && HasChildren()) + { + XMPNode[] children = (XMPNode[])Collections.ToArray(GetChildren(), new XMPNode[GetChildrenLength()]); + if (!GetOptions().IsArray()) + { + Arrays.Sort(children); + } + for (int i_1 = 0; i_1 < children.Length; i_1++) + { + XMPNode child = children[i_1]; + child.DumpNode(result, recursive, indent + 1, i_1 + 1); + } + } + } + + /// Returns whether this node is a language qualifier. + private bool IsLanguageNode() + { + return XMPConstConstants.XmlLang.Equals(name); + } + + /// Returns whether this node is a type qualifier. + private bool IsTypeNode() + { + return "rdf:type".Equals(name); + } + + /// + /// Note: This method should always be called when accessing 'children' to be sure + /// that its initialized. + /// + /// Returns list of children that is lazy initialized. + private IList GetChildren() + { + if (children == null) + { + children = new ArrayList(0); + } + return children; + } + + /// Returns a read-only copy of child nodes list. + public virtual IList GetUnmodifiableChildren() + { + return Collections.UnmodifiableList(new ArrayList(GetChildren())); + } + + /// Returns list of qualifier that is lazy initialized. + private IList GetQualifier() + { + if (qualifier == null) + { + qualifier = new ArrayList(0); + } + return qualifier; + } + + /// + /// Sets the parent node, this is solely done by addChild(...) + /// and addQualifier(). + /// + /// Sets the parent node. + protected internal virtual void SetParent(XMPNode parent) + { + this.parent = parent; + } + + /// Internal find. + /// the list to search in + /// the search expression + /// Returns the found node or nulls. + private XMPNode Find(IList list, string expr) + { + if (list != null) + { + for (Iterator it = list.Iterator(); it.HasNext(); ) + { + XMPNode child = (XMPNode)it.Next(); + if (child.GetName().Equals(expr)) + { + return child; + } + } + } + return null; + } + + /// Checks that a node name is not existing on the same level, except for array items. + /// the node name to check + /// Thrown if a node with the same name is existing. + private void AssertChildNotExisting(string childName) + { + if (!XMPConstConstants.ArrayItemName.Equals(childName) && FindChildByName(childName) != null) + { + throw new XMPException("Duplicate property or field node '" + childName + "'", XMPErrorConstants.Badxmp); + } + } + + /// Checks that a qualifier name is not existing on the same level. + /// the new qualifier name + /// Thrown if a node with the same name is existing. + private void AssertQualifierNotExisting(string qualifierName) + { + if (!XMPConstConstants.ArrayItemName.Equals(qualifierName) && FindQualifierByName(qualifierName) != null) + { + throw new XMPException("Duplicate '" + qualifierName + "' qualifier", XMPErrorConstants.Badxmp); + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNodeUtils.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNodeUtils.cs index fed589358..f7457395b 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNodeUtils.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNodeUtils.cs @@ -1,872 +1,872 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System; -using System.Diagnostics; -using Com.Adobe.Xmp.Impl.Xpath; -using Com.Adobe.Xmp.Options; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// Utilities for XMPNode. - /// Aug 28, 2006 - public class XMPNodeUtils : XMPConst - { - internal const int CltNoValues = 0; - - internal const int CltSpecificMatch = 1; - - internal const int CltSingleGeneric = 2; - - internal const int CltMultipleGeneric = 3; - - internal const int CltXdefault = 4; - - internal const int CltFirstItem = 5; - - /// Private Constructor - private XMPNodeUtils() - { - } - - // EMPTY - /// Find or create a schema node if createNodes is false and - /// the root of the xmp tree. - /// a namespace - /// - /// a flag indicating if the node shall be created if not found. - /// Note: The namespace must be registered prior to this call. - /// - /// - /// Returns the schema node if found, null otherwise. - /// Note: If createNodes is true, it is always - /// returned a valid node. - /// - /// - /// An exception is only thrown if an error occurred, not if a - /// node was not found. - /// - internal static XMPNode FindSchemaNode(XMPNode tree, string namespaceURI, bool createNodes) - { - return FindSchemaNode(tree, namespaceURI, null, createNodes); - } - - /// Find or create a schema node if createNodes is true. - /// the root of the xmp tree. - /// a namespace - /// If a prefix is suggested, the namespace is allowed to be registered. - /// - /// a flag indicating if the node shall be created if not found. - /// Note: The namespace must be registered prior to this call. - /// - /// - /// Returns the schema node if found, null otherwise. - /// Note: If createNodes is true, it is always - /// returned a valid node. - /// - /// - /// An exception is only thrown if an error occurred, not if a - /// node was not found. - /// - internal static XMPNode FindSchemaNode(XMPNode tree, string namespaceURI, string suggestedPrefix, bool createNodes) - { - Debug.Assert(tree.GetParent() == null); - // make sure that its the root - XMPNode schemaNode = tree.FindChildByName(namespaceURI); - if (schemaNode == null && createNodes) - { - schemaNode = new XMPNode(namespaceURI, new PropertyOptions().SetSchemaNode(true)); - schemaNode.SetImplicit(true); - // only previously registered schema namespaces are allowed in the XMP tree. - string prefix = XMPMetaFactory.GetSchemaRegistry().GetNamespacePrefix(namespaceURI); - if (prefix == null) - { - if (suggestedPrefix != null && suggestedPrefix.Length != 0) - { - prefix = XMPMetaFactory.GetSchemaRegistry().RegisterNamespace(namespaceURI, suggestedPrefix); - } - else - { - throw new XMPException("Unregistered schema namespace URI", XMPErrorConstants.Badschema); - } - } - schemaNode.SetValue(prefix); - tree.AddChild(schemaNode); - } - return schemaNode; - } - - /// Find or create a child node under a given parent node. - /// - /// Find or create a child node under a given parent node. If the parent node is no - /// Returns the found or created child node. - /// - /// the parent node - /// the node name to find - /// flag, if new nodes shall be created. - /// Returns the found or created node or null. - /// Thrown if - internal static XMPNode FindChildNode(XMPNode parent, string childName, bool createNodes) - { - if (!parent.GetOptions().IsSchemaNode() && !parent.GetOptions().IsStruct()) - { - if (!parent.IsImplicit()) - { - throw new XMPException("Named children only allowed for schemas and structs", XMPErrorConstants.Badxpath); - } - else - { - if (parent.GetOptions().IsArray()) - { - throw new XMPException("Named children not allowed for arrays", XMPErrorConstants.Badxpath); - } - else - { - if (createNodes) - { - parent.GetOptions().SetStruct(true); - } - } - } - } - XMPNode childNode = parent.FindChildByName(childName); - if (childNode == null && createNodes) - { - PropertyOptions options = new PropertyOptions(); - childNode = new XMPNode(childName, options); - childNode.SetImplicit(true); - parent.AddChild(childNode); - } - Debug.Assert(childNode != null || !createNodes); - return childNode; - } - - /// Follow an expanded path expression to find or create a node. - /// the node to begin the search. - /// the complete xpath - /// - /// flag if nodes shall be created - /// (when called by setProperty()) - /// - /// - /// the options for the created leaf nodes (only when - /// createNodes == true). - /// - /// Returns the node if found or created or null. - /// - /// An exception is only thrown if an error occurred, - /// not if a node was not found. - /// - internal static XMPNode FindNode(XMPNode xmpTree, XMPPath xpath, bool createNodes, PropertyOptions leafOptions) - { - // check if xpath is set. - if (xpath == null || xpath.Size() == 0) - { - throw new XMPException("Empty XMPPath", XMPErrorConstants.Badxpath); - } - // Root of implicitly created subtree to possible delete it later. - // Valid only if leaf is new. - XMPNode rootImplicitNode = null; - XMPNode currNode = null; - // resolve schema step - currNode = FindSchemaNode(xmpTree, xpath.GetSegment(XMPPath.StepSchema).GetName(), createNodes); - if (currNode == null) - { - return null; - } - else - { - if (currNode.IsImplicit()) - { - currNode.SetImplicit(false); - // Clear the implicit node bit. - rootImplicitNode = currNode; - } - } - // Save the top most implicit node. - // Now follow the remaining steps of the original XMPPath. - try - { - for (int i = 1; i < xpath.Size(); i++) - { - currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes); - if (currNode == null) - { - if (createNodes) - { - // delete implicitly created nodes - DeleteNode(rootImplicitNode); - } - return null; - } - else - { - if (currNode.IsImplicit()) - { - // clear the implicit node flag - currNode.SetImplicit(false); - // if node is an ALIAS (can be only in root step, auto-create array - // when the path has been resolved from a not simple alias type - if (i == 1 && xpath.GetSegment(i).IsAlias() && xpath.GetSegment(i).GetAliasForm() != 0) - { - currNode.GetOptions().SetOption(xpath.GetSegment(i).GetAliasForm(), true); - } - else - { - // "CheckImplicitStruct" in C++ - if (i < xpath.Size() - 1 && xpath.GetSegment(i).GetKind() == XMPPath.StructFieldStep && !currNode.GetOptions().IsCompositeProperty()) - { - currNode.GetOptions().SetStruct(true); - } - } - if (rootImplicitNode == null) - { - rootImplicitNode = currNode; - } - } - } - } - } - catch (XMPException e) - { - // Save the top most implicit node. - // if new notes have been created prior to the error, delete them - if (rootImplicitNode != null) - { - DeleteNode(rootImplicitNode); - } - throw; - } - if (rootImplicitNode != null) - { - // set options only if a node has been successful created - currNode.GetOptions().MergeWith(leafOptions); - currNode.SetOptions(currNode.GetOptions()); - } - return currNode; - } - - /// Deletes the the given node and its children from its parent. - /// - /// Deletes the the given node and its children from its parent. - /// Takes care about adjusting the flags. - /// - /// the top-most node to delete. - internal static void DeleteNode(XMPNode node) - { - XMPNode parent = node.GetParent(); - if (node.GetOptions().IsQualifier()) - { - // root is qualifier - parent.RemoveQualifier(node); - } - else - { - // root is NO qualifier - parent.RemoveChild(node); - } - // delete empty Schema nodes - if (!parent.HasChildren() && parent.GetOptions().IsSchemaNode()) - { - parent.GetParent().RemoveChild(parent); - } - } - - /// This is setting the value of a leaf node. - /// an XMPNode - /// a value - internal static void SetNodeValue(XMPNode node, object value) - { - string strValue = SerializeNodeValue(value); - if (!(node.GetOptions().IsQualifier() && XMPConstConstants.XmlLang.Equals(node.GetName()))) - { - node.SetValue(strValue); - } - else - { - node.SetValue(Utils.NormalizeLangValue(strValue)); - } - } - - /// Verifies the PropertyOptions for consistancy and updates them as needed. - /// - /// Verifies the PropertyOptions for consistancy and updates them as needed. - /// If options are null they are created with default values. - /// - /// the PropertyOptions - /// the node value to set - /// Returns the updated options. - /// If the options are not consistant. - internal static PropertyOptions VerifySetOptions(PropertyOptions options, object itemValue) - { - // create empty and fix existing options - if (options == null) - { - // set default options - options = new PropertyOptions(); - } - if (options.IsArrayAltText()) - { - options.SetArrayAlternate(true); - } - if (options.IsArrayAlternate()) - { - options.SetArrayOrdered(true); - } - if (options.IsArrayOrdered()) - { - options.SetArray(true); - } - if (options.IsCompositeProperty() && itemValue != null && itemValue.ToString().Length > 0) - { - throw new XMPException("Structs and arrays can't have values", XMPErrorConstants.Badoptions); - } - options.AssertConsistency(options.GetOptions()); - return options; - } - - /// - /// Converts the node value to String, apply special conversions for defined - /// types in XMP. - /// - /// the node value to set - /// Returns the String representation of the node value. - internal static string SerializeNodeValue(object value) - { - string strValue; - if (value == null) - { - strValue = null; - } - else - { - if (value is bool) - { - strValue = XMPUtils.ConvertFromBoolean(((bool)value)); - } - else - { - if (value is int) - { - strValue = XMPUtils.ConvertFromInteger(((int)value).IntValue()); - } - else - { - if (value is long) - { - strValue = XMPUtils.ConvertFromLong(((long)value).LongValue()); - } - else - { - if (value is double) - { - strValue = XMPUtils.ConvertFromDouble(((double)value).DoubleValue()); - } - else - { - if (value is XMPDateTime) - { - strValue = XMPUtils.ConvertFromDate((XMPDateTime)value); - } - else - { - if (value is GregorianCalendar) - { - XMPDateTime dt = XMPDateTimeFactory.CreateFromCalendar((GregorianCalendar)value); - strValue = XMPUtils.ConvertFromDate(dt); - } - else - { - if (value is sbyte[]) - { - strValue = XMPUtils.EncodeBase64((sbyte[])value); - } - else - { - strValue = value.ToString(); - } - } - } - } - } - } - } - } - return strValue != null ? Utils.RemoveControlChars(strValue) : null; - } - - /// - /// After processing by ExpandXPath, a step can be of these forms: - ///
    - ///
  • qualName - A top level property or struct field. - ///
- /// - /// After processing by ExpandXPath, a step can be of these forms: - ///
    - ///
  • qualName - A top level property or struct field. - ///
  • [index] - An element of an array. - ///
  • [last()] - The last element of an array. - ///
  • [qualName="value"] - An element in an array of structs, chosen by a field value. - ///
  • [?qualName="value"] - An element in an array, chosen by a qualifier value. - ///
  • ?qualName - A general qualifier. - ///
- /// Find the appropriate child node, resolving aliases, and optionally creating nodes. - ///
- /// the node to start to start from - /// the xpath segment - /// - /// returns the found or created XMPPath node - /// - private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) - { - XMPNode nextNode = null; - int index = 0; - int stepKind = nextStep.GetKind(); - if (stepKind == XMPPath.StructFieldStep) - { - nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); - } - else - { - if (stepKind == XMPPath.QualifierStep) - { - nextNode = FindQualifierNode(parentNode, Runtime.Substring(nextStep.GetName(), 1), createNodes); - } - else - { - // This is an array indexing step. First get the index, then get the node. - if (!parentNode.GetOptions().IsArray()) - { - throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); - } - if (stepKind == XMPPath.ArrayIndexStep) - { - index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); - } - else - { - if (stepKind == XMPPath.ArrayLastStep) - { - index = parentNode.GetChildrenLength(); - } - else - { - if (stepKind == XMPPath.FieldSelectorStep) - { - string[] result = Utils.SplitNameAndValue(nextStep.GetName()); - string fieldName = result[0]; - string fieldValue = result[1]; - index = LookupFieldSelector(parentNode, fieldName, fieldValue); - } - else - { - if (stepKind == XMPPath.QualSelectorStep) - { - string[] result = Utils.SplitNameAndValue(nextStep.GetName()); - string qualName = result[0]; - string qualValue = result[1]; - index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); - } - else - { - throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); - } - } - } - } - if (1 <= index && index <= parentNode.GetChildrenLength()) - { - nextNode = parentNode.GetChild(index); - } - } - } - return nextNode; - } - - /// Find or create a qualifier node under a given parent node. - /// - /// Find or create a qualifier node under a given parent node. Returns a pointer to the - /// qualifier node, and optionally an iterator for the node's position in - /// the parent's vector of qualifiers. The iterator is unchanged if no qualifier node (null) - /// is returned. - /// Note: On entry, the qualName parameter must not have the leading '?' from the - /// XMPPath step. - /// - /// the parent XMPNode - /// the qualifier name - /// flag if nodes shall be created - /// Returns the qualifier node if found or created, null otherwise. - /// - private static XMPNode FindQualifierNode(XMPNode parent, string qualName, bool createNodes) - { - Debug.Assert(!qualName.StartsWith("?")); - XMPNode qualNode = parent.FindQualifierByName(qualName); - if (qualNode == null && createNodes) - { - qualNode = new XMPNode(qualName, null); - qualNode.SetImplicit(true); - parent.AddQualifier(qualNode); - } - return qualNode; - } - - /// an array node - /// the segment containing the array index - /// flag if new nodes are allowed to be created. - /// Returns the index or index = -1 if not found - /// Throws Exceptions - private static int FindIndexedItem(XMPNode arrayNode, string segment, bool createNodes) - { - int index = 0; - try - { - segment = Runtime.Substring(segment, 1, segment.Length - 1); - index = Convert.ToInt32(segment); - if (index < 1) - { - throw new XMPException("Array index must be larger than zero", XMPErrorConstants.Badxpath); - } - } - catch (FormatException) - { - throw new XMPException("Array index not digits.", XMPErrorConstants.Badxpath); - } - if (createNodes && index == arrayNode.GetChildrenLength() + 1) - { - // Append a new last + 1 node. - XMPNode newItem = new XMPNode(XMPConstConstants.ArrayItemName, null); - newItem.SetImplicit(true); - arrayNode.AddChild(newItem); - } - return index; - } - - /// - /// Searches for a field selector in a node: - /// [fieldName="value] - an element in an array of structs, chosen by a field value. - /// - /// - /// Searches for a field selector in a node: - /// [fieldName="value] - an element in an array of structs, chosen by a field value. - /// No implicit nodes are created by field selectors. - /// - /// - /// - /// - /// Returns the index of the field if found, otherwise -1. - /// - private static int LookupFieldSelector(XMPNode arrayNode, string fieldName, string fieldValue) - { - int result = -1; - for (int index = 1; index <= arrayNode.GetChildrenLength() && result < 0; index++) - { - XMPNode currItem = arrayNode.GetChild(index); - if (!currItem.GetOptions().IsStruct()) - { - throw new XMPException("Field selector must be used on array of struct", XMPErrorConstants.Badxpath); - } - for (int f = 1; f <= currItem.GetChildrenLength(); f++) - { - XMPNode currField = currItem.GetChild(f); - if (!fieldName.Equals(currField.GetName())) - { - continue; - } - if (fieldValue.Equals(currField.GetValue())) - { - result = index; - break; - } - } - } - return result; - } - - /// - /// Searches for a qualifier selector in a node: - /// [?qualName="value"] - an element in an array, chosen by a qualifier value. - /// - /// - /// Searches for a qualifier selector in a node: - /// [?qualName="value"] - an element in an array, chosen by a qualifier value. - /// No implicit nodes are created for qualifier selectors, - /// except for an alias to an x-default item. - /// - /// an array node - /// the qualifier name - /// the qualifier value - /// - /// in case the qual selector results from an alias, - /// an x-default node is created if there has not been one. - /// - /// Returns the index of th - /// - private static int LookupQualSelector(XMPNode arrayNode, string qualName, string qualValue, int aliasForm) - { - if (XMPConstConstants.XmlLang.Equals(qualName)) - { - qualValue = Utils.NormalizeLangValue(qualValue); - int index = LookupLanguageItem(arrayNode, qualValue); - if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0) - { - XMPNode langNode = new XMPNode(XMPConstConstants.ArrayItemName, null); - XMPNode xdefault = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); - langNode.AddQualifier(xdefault); - arrayNode.AddChild(1, langNode); - return 1; - } - else - { - return index; - } - } - else - { - for (int index = 1; index < arrayNode.GetChildrenLength(); index++) - { - XMPNode currItem = arrayNode.GetChild(index); - for (Iterator it = currItem.IterateQualifier(); it.HasNext(); ) - { - XMPNode qualifier = (XMPNode)it.Next(); - if (qualName.Equals(qualifier.GetName()) && qualValue.Equals(qualifier.GetValue())) - { - return index; - } - } - } - return -1; - } - } - - /// Make sure the x-default item is first. - /// - /// Make sure the x-default item is first. Touch up "single value" - /// arrays that have a default plus one real language. This case should have - /// the same value for both items. Older Adobe apps were hardwired to only - /// use the "x-default" item, so we copy that value to the other - /// item. - /// - /// an alt text array node - internal static void NormalizeLangArray(XMPNode arrayNode) - { - if (!arrayNode.GetOptions().IsArrayAltText()) - { - return; - } - // check if node with x-default qual is first place - for (int i = 2; i <= arrayNode.GetChildrenLength(); i++) - { - XMPNode child = arrayNode.GetChild(i); - if (child.HasQualifier() && XMPConstConstants.XDefault.Equals(child.GetQualifier(1).GetValue())) - { - // move node to first place - try - { - arrayNode.RemoveChild(i); - arrayNode.AddChild(1, child); - } - catch (XMPException) - { - // cannot occur, because same child is removed before - Debug.Assert(false); - } - if (i == 2) - { - arrayNode.GetChild(2).SetValue(child.GetValue()); - } - break; - } - } - } - - /// See if an array is an alt-text array. - /// - /// See if an array is an alt-text array. If so, make sure the x-default item - /// is first. - /// - /// the array node to check if its an alt-text array - internal static void DetectAltText(XMPNode arrayNode) - { - if (arrayNode.GetOptions().IsArrayAlternate() && arrayNode.HasChildren()) - { - bool isAltText = false; - for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) - { - XMPNode child = (XMPNode)it.Next(); - if (child.GetOptions().GetHasLanguage()) - { - isAltText = true; - break; - } - } - if (isAltText) - { - arrayNode.GetOptions().SetArrayAltText(true); - NormalizeLangArray(arrayNode); - } - } - } - - /// Appends a language item to an alt text array. - /// the language array - /// the language of the item - /// the content of the item - /// Thrown if a duplicate property is added - internal static void AppendLangItem(XMPNode arrayNode, string itemLang, string itemValue) - { - XMPNode newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); - XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, itemLang, null); - newItem.AddQualifier(langQual); - if (!XMPConstConstants.XDefault.Equals(langQual.GetValue())) - { - arrayNode.AddChild(newItem); - } - else - { - arrayNode.AddChild(1, newItem); - } - } - - /// - ///
    - ///
  1. Look for an exact match with the specific language. - ///
- /// - ///
    - ///
  1. Look for an exact match with the specific language. - ///
  2. If a generic language is given, look for partial matches. - ///
  3. Look for an "x-default"-item. - ///
  4. Choose the first item. - ///
- ///
- /// the alt text array node - /// the generic language - /// the specific language - /// - /// Returns the kind of match as an Integer and the found node in an - /// array. - /// - /// - internal static object[] ChooseLocalizedText(XMPNode arrayNode, string genericLang, string specificLang) - { - // See if the array has the right form. Allow empty alt arrays, - // that is what parsing returns. - if (!arrayNode.GetOptions().IsArrayAltText()) - { - throw new XMPException("Localized text array is not alt-text", XMPErrorConstants.Badxpath); - } - else - { - if (!arrayNode.HasChildren()) - { - return new object[] { CltNoValues, null }; - } - } - int foundGenericMatches = 0; - XMPNode resultNode = null; - XMPNode xDefault = null; - // Look for the first partial match with the generic language. - for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) - { - XMPNode currItem = (XMPNode)it.Next(); - // perform some checks on the current item - if (currItem.GetOptions().IsCompositeProperty()) - { - throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); - } - else - { - if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) - { - throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); - } - } - string currLang = currItem.GetQualifier(1).GetValue(); - // Look for an exact match with the specific language. - if (specificLang.Equals(currLang)) - { - return new object[] { CltSpecificMatch, currItem }; - } - else - { - if (genericLang != null && currLang.StartsWith(genericLang)) - { - if (resultNode == null) - { - resultNode = currItem; - } - // ! Don't return/break, need to look for other matches. - foundGenericMatches++; - } - else - { - if (XMPConstConstants.XDefault.Equals(currLang)) - { - xDefault = currItem; - } - } - } - } - // evaluate loop - if (foundGenericMatches == 1) - { - return new object[] { CltSingleGeneric, resultNode }; - } - else - { - if (foundGenericMatches > 1) - { - return new object[] { CltMultipleGeneric, resultNode }; - } - else - { - if (xDefault != null) - { - return new object[] { CltXdefault, xDefault }; - } - else - { - // Everything failed, choose the first item. - return new object[] { CltFirstItem, arrayNode.GetChild(1) }; - } - } - } - } - - /// Looks for the appropriate language item in a text alternative array.item - /// an array node - /// the requested language - /// Returns the index if the language has been found, -1 otherwise. - /// - internal static int LookupLanguageItem(XMPNode arrayNode, string language) - { - if (!arrayNode.GetOptions().IsArray()) - { - throw new XMPException("Language item must be used on array", XMPErrorConstants.Badxpath); - } - for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) - { - XMPNode child = arrayNode.GetChild(index); - if (!child.HasQualifier() || !XMPConstConstants.XmlLang.Equals(child.GetQualifier(1).GetName())) - { - continue; - } - else - { - if (language.Equals(child.GetQualifier(1).GetValue())) - { - return index; - } - } - } - return -1; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System; +using System.Diagnostics; +using Com.Adobe.Xmp.Impl.Xpath; +using Com.Adobe.Xmp.Options; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// Utilities for XMPNode. + /// Aug 28, 2006 + public class XMPNodeUtils : XMPConst + { + internal const int CltNoValues = 0; + + internal const int CltSpecificMatch = 1; + + internal const int CltSingleGeneric = 2; + + internal const int CltMultipleGeneric = 3; + + internal const int CltXdefault = 4; + + internal const int CltFirstItem = 5; + + /// Private Constructor + private XMPNodeUtils() + { + } + + // EMPTY + /// Find or create a schema node if createNodes is false and + /// the root of the xmp tree. + /// a namespace + /// + /// a flag indicating if the node shall be created if not found. + /// Note: The namespace must be registered prior to this call. + /// + /// + /// Returns the schema node if found, null otherwise. + /// Note: If createNodes is true, it is always + /// returned a valid node. + /// + /// + /// An exception is only thrown if an error occurred, not if a + /// node was not found. + /// + internal static XMPNode FindSchemaNode(XMPNode tree, string namespaceURI, bool createNodes) + { + return FindSchemaNode(tree, namespaceURI, null, createNodes); + } + + /// Find or create a schema node if createNodes is true. + /// the root of the xmp tree. + /// a namespace + /// If a prefix is suggested, the namespace is allowed to be registered. + /// + /// a flag indicating if the node shall be created if not found. + /// Note: The namespace must be registered prior to this call. + /// + /// + /// Returns the schema node if found, null otherwise. + /// Note: If createNodes is true, it is always + /// returned a valid node. + /// + /// + /// An exception is only thrown if an error occurred, not if a + /// node was not found. + /// + internal static XMPNode FindSchemaNode(XMPNode tree, string namespaceURI, string suggestedPrefix, bool createNodes) + { + Debug.Assert(tree.GetParent() == null); + // make sure that its the root + XMPNode schemaNode = tree.FindChildByName(namespaceURI); + if (schemaNode == null && createNodes) + { + schemaNode = new XMPNode(namespaceURI, new PropertyOptions().SetSchemaNode(true)); + schemaNode.SetImplicit(true); + // only previously registered schema namespaces are allowed in the XMP tree. + string prefix = XMPMetaFactory.GetSchemaRegistry().GetNamespacePrefix(namespaceURI); + if (prefix == null) + { + if (suggestedPrefix != null && suggestedPrefix.Length != 0) + { + prefix = XMPMetaFactory.GetSchemaRegistry().RegisterNamespace(namespaceURI, suggestedPrefix); + } + else + { + throw new XMPException("Unregistered schema namespace URI", XMPErrorConstants.Badschema); + } + } + schemaNode.SetValue(prefix); + tree.AddChild(schemaNode); + } + return schemaNode; + } + + /// Find or create a child node under a given parent node. + /// + /// Find or create a child node under a given parent node. If the parent node is no + /// Returns the found or created child node. + /// + /// the parent node + /// the node name to find + /// flag, if new nodes shall be created. + /// Returns the found or created node or null. + /// Thrown if + internal static XMPNode FindChildNode(XMPNode parent, string childName, bool createNodes) + { + if (!parent.GetOptions().IsSchemaNode() && !parent.GetOptions().IsStruct()) + { + if (!parent.IsImplicit()) + { + throw new XMPException("Named children only allowed for schemas and structs", XMPErrorConstants.Badxpath); + } + else + { + if (parent.GetOptions().IsArray()) + { + throw new XMPException("Named children not allowed for arrays", XMPErrorConstants.Badxpath); + } + else + { + if (createNodes) + { + parent.GetOptions().SetStruct(true); + } + } + } + } + XMPNode childNode = parent.FindChildByName(childName); + if (childNode == null && createNodes) + { + PropertyOptions options = new PropertyOptions(); + childNode = new XMPNode(childName, options); + childNode.SetImplicit(true); + parent.AddChild(childNode); + } + Debug.Assert(childNode != null || !createNodes); + return childNode; + } + + /// Follow an expanded path expression to find or create a node. + /// the node to begin the search. + /// the complete xpath + /// + /// flag if nodes shall be created + /// (when called by setProperty()) + /// + /// + /// the options for the created leaf nodes (only when + /// createNodes == true). + /// + /// Returns the node if found or created or null. + /// + /// An exception is only thrown if an error occurred, + /// not if a node was not found. + /// + internal static XMPNode FindNode(XMPNode xmpTree, XMPPath xpath, bool createNodes, PropertyOptions leafOptions) + { + // check if xpath is set. + if (xpath == null || xpath.Size() == 0) + { + throw new XMPException("Empty XMPPath", XMPErrorConstants.Badxpath); + } + // Root of implicitly created subtree to possible delete it later. + // Valid only if leaf is new. + XMPNode rootImplicitNode = null; + XMPNode currNode = null; + // resolve schema step + currNode = FindSchemaNode(xmpTree, xpath.GetSegment(XMPPath.StepSchema).GetName(), createNodes); + if (currNode == null) + { + return null; + } + else + { + if (currNode.IsImplicit()) + { + currNode.SetImplicit(false); + // Clear the implicit node bit. + rootImplicitNode = currNode; + } + } + // Save the top most implicit node. + // Now follow the remaining steps of the original XMPPath. + try + { + for (int i = 1; i < xpath.Size(); i++) + { + currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes); + if (currNode == null) + { + if (createNodes) + { + // delete implicitly created nodes + DeleteNode(rootImplicitNode); + } + return null; + } + else + { + if (currNode.IsImplicit()) + { + // clear the implicit node flag + currNode.SetImplicit(false); + // if node is an ALIAS (can be only in root step, auto-create array + // when the path has been resolved from a not simple alias type + if (i == 1 && xpath.GetSegment(i).IsAlias() && xpath.GetSegment(i).GetAliasForm() != 0) + { + currNode.GetOptions().SetOption(xpath.GetSegment(i).GetAliasForm(), true); + } + else + { + // "CheckImplicitStruct" in C++ + if (i < xpath.Size() - 1 && xpath.GetSegment(i).GetKind() == XMPPath.StructFieldStep && !currNode.GetOptions().IsCompositeProperty()) + { + currNode.GetOptions().SetStruct(true); + } + } + if (rootImplicitNode == null) + { + rootImplicitNode = currNode; + } + } + } + } + } + catch (XMPException e) + { + // Save the top most implicit node. + // if new notes have been created prior to the error, delete them + if (rootImplicitNode != null) + { + DeleteNode(rootImplicitNode); + } + throw; + } + if (rootImplicitNode != null) + { + // set options only if a node has been successful created + currNode.GetOptions().MergeWith(leafOptions); + currNode.SetOptions(currNode.GetOptions()); + } + return currNode; + } + + /// Deletes the the given node and its children from its parent. + /// + /// Deletes the the given node and its children from its parent. + /// Takes care about adjusting the flags. + /// + /// the top-most node to delete. + internal static void DeleteNode(XMPNode node) + { + XMPNode parent = node.GetParent(); + if (node.GetOptions().IsQualifier()) + { + // root is qualifier + parent.RemoveQualifier(node); + } + else + { + // root is NO qualifier + parent.RemoveChild(node); + } + // delete empty Schema nodes + if (!parent.HasChildren() && parent.GetOptions().IsSchemaNode()) + { + parent.GetParent().RemoveChild(parent); + } + } + + /// This is setting the value of a leaf node. + /// an XMPNode + /// a value + internal static void SetNodeValue(XMPNode node, object value) + { + string strValue = SerializeNodeValue(value); + if (!(node.GetOptions().IsQualifier() && XMPConstConstants.XmlLang.Equals(node.GetName()))) + { + node.SetValue(strValue); + } + else + { + node.SetValue(Utils.NormalizeLangValue(strValue)); + } + } + + /// Verifies the PropertyOptions for consistancy and updates them as needed. + /// + /// Verifies the PropertyOptions for consistancy and updates them as needed. + /// If options are null they are created with default values. + /// + /// the PropertyOptions + /// the node value to set + /// Returns the updated options. + /// If the options are not consistant. + internal static PropertyOptions VerifySetOptions(PropertyOptions options, object itemValue) + { + // create empty and fix existing options + if (options == null) + { + // set default options + options = new PropertyOptions(); + } + if (options.IsArrayAltText()) + { + options.SetArrayAlternate(true); + } + if (options.IsArrayAlternate()) + { + options.SetArrayOrdered(true); + } + if (options.IsArrayOrdered()) + { + options.SetArray(true); + } + if (options.IsCompositeProperty() && itemValue != null && itemValue.ToString().Length > 0) + { + throw new XMPException("Structs and arrays can't have values", XMPErrorConstants.Badoptions); + } + options.AssertConsistency(options.GetOptions()); + return options; + } + + /// + /// Converts the node value to String, apply special conversions for defined + /// types in XMP. + /// + /// the node value to set + /// Returns the String representation of the node value. + internal static string SerializeNodeValue(object value) + { + string strValue; + if (value == null) + { + strValue = null; + } + else + { + if (value is bool) + { + strValue = XMPUtils.ConvertFromBoolean(((bool)value)); + } + else + { + if (value is int) + { + strValue = XMPUtils.ConvertFromInteger(((int)value).IntValue()); + } + else + { + if (value is long) + { + strValue = XMPUtils.ConvertFromLong(((long)value).LongValue()); + } + else + { + if (value is double) + { + strValue = XMPUtils.ConvertFromDouble(((double)value).DoubleValue()); + } + else + { + if (value is XMPDateTime) + { + strValue = XMPUtils.ConvertFromDate((XMPDateTime)value); + } + else + { + if (value is GregorianCalendar) + { + XMPDateTime dt = XMPDateTimeFactory.CreateFromCalendar((GregorianCalendar)value); + strValue = XMPUtils.ConvertFromDate(dt); + } + else + { + if (value is sbyte[]) + { + strValue = XMPUtils.EncodeBase64((sbyte[])value); + } + else + { + strValue = value.ToString(); + } + } + } + } + } + } + } + } + return strValue != null ? Utils.RemoveControlChars(strValue) : null; + } + + /// + /// After processing by ExpandXPath, a step can be of these forms: + ///
    + ///
  • qualName - A top level property or struct field. + ///
+ /// + /// After processing by ExpandXPath, a step can be of these forms: + ///
    + ///
  • qualName - A top level property or struct field. + ///
  • [index] - An element of an array. + ///
  • [last()] - The last element of an array. + ///
  • [qualName="value"] - An element in an array of structs, chosen by a field value. + ///
  • [?qualName="value"] - An element in an array, chosen by a qualifier value. + ///
  • ?qualName - A general qualifier. + ///
+ /// Find the appropriate child node, resolving aliases, and optionally creating nodes. + ///
+ /// the node to start to start from + /// the xpath segment + /// + /// returns the found or created XMPPath node + /// + private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) + { + XMPNode nextNode = null; + int index = 0; + int stepKind = nextStep.GetKind(); + if (stepKind == XMPPath.StructFieldStep) + { + nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); + } + else + { + if (stepKind == XMPPath.QualifierStep) + { + nextNode = FindQualifierNode(parentNode, Runtime.Substring(nextStep.GetName(), 1), createNodes); + } + else + { + // This is an array indexing step. First get the index, then get the node. + if (!parentNode.GetOptions().IsArray()) + { + throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); + } + if (stepKind == XMPPath.ArrayIndexStep) + { + index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); + } + else + { + if (stepKind == XMPPath.ArrayLastStep) + { + index = parentNode.GetChildrenLength(); + } + else + { + if (stepKind == XMPPath.FieldSelectorStep) + { + string[] result = Utils.SplitNameAndValue(nextStep.GetName()); + string fieldName = result[0]; + string fieldValue = result[1]; + index = LookupFieldSelector(parentNode, fieldName, fieldValue); + } + else + { + if (stepKind == XMPPath.QualSelectorStep) + { + string[] result = Utils.SplitNameAndValue(nextStep.GetName()); + string qualName = result[0]; + string qualValue = result[1]; + index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); + } + else + { + throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); + } + } + } + } + if (1 <= index && index <= parentNode.GetChildrenLength()) + { + nextNode = parentNode.GetChild(index); + } + } + } + return nextNode; + } + + /// Find or create a qualifier node under a given parent node. + /// + /// Find or create a qualifier node under a given parent node. Returns a pointer to the + /// qualifier node, and optionally an iterator for the node's position in + /// the parent's vector of qualifiers. The iterator is unchanged if no qualifier node (null) + /// is returned. + /// Note: On entry, the qualName parameter must not have the leading '?' from the + /// XMPPath step. + /// + /// the parent XMPNode + /// the qualifier name + /// flag if nodes shall be created + /// Returns the qualifier node if found or created, null otherwise. + /// + private static XMPNode FindQualifierNode(XMPNode parent, string qualName, bool createNodes) + { + Debug.Assert(!qualName.StartsWith("?")); + XMPNode qualNode = parent.FindQualifierByName(qualName); + if (qualNode == null && createNodes) + { + qualNode = new XMPNode(qualName, null); + qualNode.SetImplicit(true); + parent.AddQualifier(qualNode); + } + return qualNode; + } + + /// an array node + /// the segment containing the array index + /// flag if new nodes are allowed to be created. + /// Returns the index or index = -1 if not found + /// Throws Exceptions + private static int FindIndexedItem(XMPNode arrayNode, string segment, bool createNodes) + { + int index = 0; + try + { + segment = Runtime.Substring(segment, 1, segment.Length - 1); + index = Convert.ToInt32(segment); + if (index < 1) + { + throw new XMPException("Array index must be larger than zero", XMPErrorConstants.Badxpath); + } + } + catch (FormatException) + { + throw new XMPException("Array index not digits.", XMPErrorConstants.Badxpath); + } + if (createNodes && index == arrayNode.GetChildrenLength() + 1) + { + // Append a new last + 1 node. + XMPNode newItem = new XMPNode(XMPConstConstants.ArrayItemName, null); + newItem.SetImplicit(true); + arrayNode.AddChild(newItem); + } + return index; + } + + /// + /// Searches for a field selector in a node: + /// [fieldName="value] - an element in an array of structs, chosen by a field value. + /// + /// + /// Searches for a field selector in a node: + /// [fieldName="value] - an element in an array of structs, chosen by a field value. + /// No implicit nodes are created by field selectors. + /// + /// + /// + /// + /// Returns the index of the field if found, otherwise -1. + /// + private static int LookupFieldSelector(XMPNode arrayNode, string fieldName, string fieldValue) + { + int result = -1; + for (int index = 1; index <= arrayNode.GetChildrenLength() && result < 0; index++) + { + XMPNode currItem = arrayNode.GetChild(index); + if (!currItem.GetOptions().IsStruct()) + { + throw new XMPException("Field selector must be used on array of struct", XMPErrorConstants.Badxpath); + } + for (int f = 1; f <= currItem.GetChildrenLength(); f++) + { + XMPNode currField = currItem.GetChild(f); + if (!fieldName.Equals(currField.GetName())) + { + continue; + } + if (fieldValue.Equals(currField.GetValue())) + { + result = index; + break; + } + } + } + return result; + } + + /// + /// Searches for a qualifier selector in a node: + /// [?qualName="value"] - an element in an array, chosen by a qualifier value. + /// + /// + /// Searches for a qualifier selector in a node: + /// [?qualName="value"] - an element in an array, chosen by a qualifier value. + /// No implicit nodes are created for qualifier selectors, + /// except for an alias to an x-default item. + /// + /// an array node + /// the qualifier name + /// the qualifier value + /// + /// in case the qual selector results from an alias, + /// an x-default node is created if there has not been one. + /// + /// Returns the index of th + /// + private static int LookupQualSelector(XMPNode arrayNode, string qualName, string qualValue, int aliasForm) + { + if (XMPConstConstants.XmlLang.Equals(qualName)) + { + qualValue = Utils.NormalizeLangValue(qualValue); + int index = LookupLanguageItem(arrayNode, qualValue); + if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0) + { + XMPNode langNode = new XMPNode(XMPConstConstants.ArrayItemName, null); + XMPNode xdefault = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); + langNode.AddQualifier(xdefault); + arrayNode.AddChild(1, langNode); + return 1; + } + else + { + return index; + } + } + else + { + for (int index = 1; index < arrayNode.GetChildrenLength(); index++) + { + XMPNode currItem = arrayNode.GetChild(index); + for (Iterator it = currItem.IterateQualifier(); it.HasNext(); ) + { + XMPNode qualifier = (XMPNode)it.Next(); + if (qualName.Equals(qualifier.GetName()) && qualValue.Equals(qualifier.GetValue())) + { + return index; + } + } + } + return -1; + } + } + + /// Make sure the x-default item is first. + /// + /// Make sure the x-default item is first. Touch up "single value" + /// arrays that have a default plus one real language. This case should have + /// the same value for both items. Older Adobe apps were hardwired to only + /// use the "x-default" item, so we copy that value to the other + /// item. + /// + /// an alt text array node + internal static void NormalizeLangArray(XMPNode arrayNode) + { + if (!arrayNode.GetOptions().IsArrayAltText()) + { + return; + } + // check if node with x-default qual is first place + for (int i = 2; i <= arrayNode.GetChildrenLength(); i++) + { + XMPNode child = arrayNode.GetChild(i); + if (child.HasQualifier() && XMPConstConstants.XDefault.Equals(child.GetQualifier(1).GetValue())) + { + // move node to first place + try + { + arrayNode.RemoveChild(i); + arrayNode.AddChild(1, child); + } + catch (XMPException) + { + // cannot occur, because same child is removed before + Debug.Assert(false); + } + if (i == 2) + { + arrayNode.GetChild(2).SetValue(child.GetValue()); + } + break; + } + } + } + + /// See if an array is an alt-text array. + /// + /// See if an array is an alt-text array. If so, make sure the x-default item + /// is first. + /// + /// the array node to check if its an alt-text array + internal static void DetectAltText(XMPNode arrayNode) + { + if (arrayNode.GetOptions().IsArrayAlternate() && arrayNode.HasChildren()) + { + bool isAltText = false; + for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) + { + XMPNode child = (XMPNode)it.Next(); + if (child.GetOptions().GetHasLanguage()) + { + isAltText = true; + break; + } + } + if (isAltText) + { + arrayNode.GetOptions().SetArrayAltText(true); + NormalizeLangArray(arrayNode); + } + } + } + + /// Appends a language item to an alt text array. + /// the language array + /// the language of the item + /// the content of the item + /// Thrown if a duplicate property is added + internal static void AppendLangItem(XMPNode arrayNode, string itemLang, string itemValue) + { + XMPNode newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); + XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, itemLang, null); + newItem.AddQualifier(langQual); + if (!XMPConstConstants.XDefault.Equals(langQual.GetValue())) + { + arrayNode.AddChild(newItem); + } + else + { + arrayNode.AddChild(1, newItem); + } + } + + /// + ///
    + ///
  1. Look for an exact match with the specific language. + ///
+ /// + ///
    + ///
  1. Look for an exact match with the specific language. + ///
  2. If a generic language is given, look for partial matches. + ///
  3. Look for an "x-default"-item. + ///
  4. Choose the first item. + ///
+ ///
+ /// the alt text array node + /// the generic language + /// the specific language + /// + /// Returns the kind of match as an Integer and the found node in an + /// array. + /// + /// + internal static object[] ChooseLocalizedText(XMPNode arrayNode, string genericLang, string specificLang) + { + // See if the array has the right form. Allow empty alt arrays, + // that is what parsing returns. + if (!arrayNode.GetOptions().IsArrayAltText()) + { + throw new XMPException("Localized text array is not alt-text", XMPErrorConstants.Badxpath); + } + else + { + if (!arrayNode.HasChildren()) + { + return new object[] { CltNoValues, null }; + } + } + int foundGenericMatches = 0; + XMPNode resultNode = null; + XMPNode xDefault = null; + // Look for the first partial match with the generic language. + for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) + { + XMPNode currItem = (XMPNode)it.Next(); + // perform some checks on the current item + if (currItem.GetOptions().IsCompositeProperty()) + { + throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); + } + else + { + if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) + { + throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); + } + } + string currLang = currItem.GetQualifier(1).GetValue(); + // Look for an exact match with the specific language. + if (specificLang.Equals(currLang)) + { + return new object[] { CltSpecificMatch, currItem }; + } + else + { + if (genericLang != null && currLang.StartsWith(genericLang)) + { + if (resultNode == null) + { + resultNode = currItem; + } + // ! Don't return/break, need to look for other matches. + foundGenericMatches++; + } + else + { + if (XMPConstConstants.XDefault.Equals(currLang)) + { + xDefault = currItem; + } + } + } + } + // evaluate loop + if (foundGenericMatches == 1) + { + return new object[] { CltSingleGeneric, resultNode }; + } + else + { + if (foundGenericMatches > 1) + { + return new object[] { CltMultipleGeneric, resultNode }; + } + else + { + if (xDefault != null) + { + return new object[] { CltXdefault, xDefault }; + } + else + { + // Everything failed, choose the first item. + return new object[] { CltFirstItem, arrayNode.GetChild(1) }; + } + } + } + } + + /// Looks for the appropriate language item in a text alternative array.item + /// an array node + /// the requested language + /// Returns the index if the language has been found, -1 otherwise. + /// + internal static int LookupLanguageItem(XMPNode arrayNode, string language) + { + if (!arrayNode.GetOptions().IsArray()) + { + throw new XMPException("Language item must be used on array", XMPErrorConstants.Badxpath); + } + for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) + { + XMPNode child = arrayNode.GetChild(index); + if (!child.HasQualifier() || !XMPConstConstants.XmlLang.Equals(child.GetQualifier(1).GetName())) + { + continue; + } + else + { + if (language.Equals(child.GetQualifier(1).GetValue())) + { + return index; + } + } + } + return -1; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNormalizer.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNormalizer.cs index cfa655708..c3d4dea34 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNormalizer.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPNormalizer.cs @@ -1,601 +1,601 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Collections; -using Com.Adobe.Xmp.Impl.Xpath; -using Com.Adobe.Xmp.Options; -using Com.Adobe.Xmp.Properties; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// Aug 18, 2006 - public class XMPNormalizer - { - /// caches the correct dc-property array forms - private static IDictionary dcArrayForms; - - static XMPNormalizer() - { - InitDCArrays(); - } - - /// Hidden constructor - private XMPNormalizer() - { - } - - // EMPTY - /// Normalizes a raw parsed XMPMeta-Object - /// the raw metadata object - /// the parsing options - /// Returns the normalized metadata object - /// Collects all severe processing errors. - internal static XMPMeta Process(XMPMetaImpl xmp, ParseOptions options) - { - XMPNode tree = xmp.GetRoot(); - TouchUpDataModel(xmp); - MoveExplicitAliases(tree, options); - TweakOldXMP(tree); - DeleteEmptySchemas(tree); - return xmp; - } - - /// - /// Tweak old XMP: Move an instance ID from rdf:about to the - /// xmpMM:InstanceID property. - /// - /// - /// Tweak old XMP: Move an instance ID from rdf:about to the - /// xmpMM:InstanceID property. An old instance ID usually looks - /// like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign - /// 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If - /// the name looks like a UUID simply move it to xmpMM:InstanceID, - /// don't worry about any existing xmpMM:InstanceID. Both will - /// only be present when a newer file with the xmpMM:InstanceID - /// property is updated by an old app that uses rdf:about. - /// - /// the root of the metadata tree - /// Thrown if tweaking fails. - private static void TweakOldXMP(XMPNode tree) - { - if (tree.GetName() != null && tree.GetName().Length >= Utils.UuidLength) - { - string nameStr = tree.GetName().ToLower(); - if (nameStr.StartsWith("uuid:")) - { - nameStr = Runtime.Substring(nameStr, 5); - } - if (Utils.CheckUUIDFormat(nameStr)) - { - // move UUID to xmpMM:InstanceID and remove it from the root node - XMPPath path = XMPPathParser.ExpandXPath(XMPConstConstants.NsXmpMm, "InstanceID"); - XMPNode idNode = XMPNodeUtils.FindNode(tree, path, true, null); - if (idNode != null) - { - idNode.SetOptions(null); - // Clobber any existing xmpMM:InstanceID. - idNode.SetValue("uuid:" + nameStr); - idNode.RemoveChildren(); - idNode.RemoveQualifiers(); - tree.SetName(null); - } - else - { - throw new XMPException("Failure creating xmpMM:InstanceID", XMPErrorConstants.Internalfailure); - } - } - } - } - - /// Visit all schemas to do general fixes and handle special cases. - /// the metadata object implementation - /// Thrown if the normalisation fails. - private static void TouchUpDataModel(XMPMetaImpl xmp) - { - // make sure the DC schema is existing, because it might be needed within the normalization - // if not touched it will be removed by removeEmptySchemas - XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), XMPConstConstants.NsDc, true); - // Do the special case fixes within each schema. - for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); ) - { - XMPNode currSchema = (XMPNode)it.Next(); - if (XMPConstConstants.NsDc.Equals(currSchema.GetName())) - { - NormalizeDCArrays(currSchema); - } - else - { - if (XMPConstConstants.NsExif.Equals(currSchema.GetName())) - { - // Do a special case fix for exif:GPSTimeStamp. - FixGPSTimeStamp(currSchema); - XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "exif:UserComment", false); - if (arrayNode != null) - { - RepairAltText(arrayNode); - } - } - else - { - if (XMPConstConstants.NsDm.Equals(currSchema.GetName())) - { - // Do a special case migration of xmpDM:copyright to - // dc:rights['x-default']. - XMPNode dmCopyright = XMPNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false); - if (dmCopyright != null) - { - MigrateAudioCopyright(xmp, dmCopyright); - } - } - else - { - if (XMPConstConstants.NsXmpRights.Equals(currSchema.GetName())) - { - XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false); - if (arrayNode != null) - { - RepairAltText(arrayNode); - } - } - } - } - } - } - } - - /// - /// Undo the denormalization performed by the XMP used in Acrobat 5.
- /// If a Dublin Core array had only one item, it was serialized as a simple - /// property. - ///
- /// - /// Undo the denormalization performed by the XMP used in Acrobat 5.
- /// If a Dublin Core array had only one item, it was serialized as a simple - /// property.
- /// The xml:lang attribute was dropped from an - /// alt-text item if the language was x-default. - ///
- /// the DC schema node - /// Thrown if normalization fails - private static void NormalizeDCArrays(XMPNode dcSchema) - { - for (int i = 1; i <= dcSchema.GetChildrenLength(); i++) - { - XMPNode currProp = dcSchema.GetChild(i); - PropertyOptions arrayForm = (PropertyOptions)dcArrayForms.Get(currProp.GetName()); - if (arrayForm == null) - { - continue; - } - else - { - if (currProp.GetOptions().IsSimple()) - { - // create a new array and add the current property as child, - // if it was formerly simple - XMPNode newArray = new XMPNode(currProp.GetName(), arrayForm); - currProp.SetName(XMPConstConstants.ArrayItemName); - newArray.AddChild(currProp); - dcSchema.ReplaceChild(i, newArray); - // fix language alternatives - if (arrayForm.IsArrayAltText() && !currProp.GetOptions().GetHasLanguage()) - { - XMPNode newLang = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); - currProp.AddQualifier(newLang); - } - } - else - { - // clear array options and add corrected array form if it has been an array before - currProp.GetOptions().SetOption(PropertyOptions.Array | PropertyOptions.ArrayOrdered | PropertyOptions.ArrayAlternate | PropertyOptions.ArrayAltText, false); - currProp.GetOptions().MergeWith(arrayForm); - if (arrayForm.IsArrayAltText()) - { - // applying for "dc:description", "dc:rights", "dc:title" - RepairAltText(currProp); - } - } - } - } - } - - /// Make sure that the array is well-formed AltText. - /// - /// Make sure that the array is well-formed AltText. Each item must be simple - /// and have an "xml:lang" qualifier. If repairs are needed, keep simple - /// non-empty items by adding the "xml:lang" with value "x-repair". - /// - /// the property node of the array to repair. - /// Forwards unexpected exceptions. - private static void RepairAltText(XMPNode arrayNode) - { - if (arrayNode == null || !arrayNode.GetOptions().IsArray()) - { - // Already OK or not even an array. - return; - } - // fix options - arrayNode.GetOptions().SetArrayOrdered(true).SetArrayAlternate(true).SetArrayAltText(true); - for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) - { - XMPNode currChild = (XMPNode)it.Next(); - if (currChild.GetOptions().IsCompositeProperty()) - { - // Delete non-simple children. - it.Remove(); - } - else - { - if (!currChild.GetOptions().GetHasLanguage()) - { - string childValue = currChild.GetValue(); - if (childValue == null || childValue.Length == 0) - { - // Delete empty valued children that have no xml:lang. - it.Remove(); - } - else - { - // Add an xml:lang qualifier with the value "x-repair". - XMPNode repairLang = new XMPNode(XMPConstConstants.XmlLang, "x-repair", null); - currChild.AddQualifier(repairLang); - } - } - } - } - } - - /// Visit all of the top level nodes looking for aliases. - /// - /// Visit all of the top level nodes looking for aliases. If there is - /// no base, transplant the alias subtree. If there is a base and strict - /// aliasing is on, make sure the alias and base subtrees match. - /// - /// the root of the metadata tree - /// th parsing options - /// Forwards XMP errors - private static void MoveExplicitAliases(XMPNode tree, ParseOptions options) - { - if (!tree.GetHasAliases()) - { - return; - } - tree.SetHasAliases(false); - bool strictAliasing = options.GetStrictAliasing(); - for (Iterator schemaIt = tree.GetUnmodifiableChildren().Iterator(); schemaIt.HasNext(); ) - { - XMPNode currSchema = (XMPNode)schemaIt.Next(); - if (!currSchema.GetHasAliases()) - { - continue; - } - for (Iterator propertyIt = currSchema.IterateChildren(); propertyIt.HasNext(); ) - { - XMPNode currProp = (XMPNode)propertyIt.Next(); - if (!currProp.IsAlias()) - { - continue; - } - currProp.SetAlias(false); - // Find the base path, look for the base schema and root node. - XMPAliasInfo info = XMPMetaFactory.GetSchemaRegistry().FindAlias(currProp.GetName()); - if (info != null) - { - // find or create schema - XMPNode baseSchema = XMPNodeUtils.FindSchemaNode(tree, info.GetNamespace(), null, true); - baseSchema.SetImplicit(false); - XMPNode baseNode = XMPNodeUtils.FindChildNode(baseSchema, info.GetPrefix() + info.GetPropName(), false); - if (baseNode == null) - { - if (info.GetAliasForm().IsSimple()) - { - // A top-to-top alias, transplant the property. - // change the alias property name to the base name - string qname = info.GetPrefix() + info.GetPropName(); - currProp.SetName(qname); - baseSchema.AddChild(currProp); - // remove the alias property - propertyIt.Remove(); - } - else - { - // An alias to an array item, - // create the array and transplant the property. - baseNode = new XMPNode(info.GetPrefix() + info.GetPropName(), info.GetAliasForm().ToPropertyOptions()); - baseSchema.AddChild(baseNode); - TransplantArrayItemAlias(propertyIt, currProp, baseNode); - } - } - else - { - if (info.GetAliasForm().IsSimple()) - { - // The base node does exist and this is a top-to-top alias. - // Check for conflicts if strict aliasing is on. - // Remove and delete the alias subtree. - if (strictAliasing) - { - CompareAliasedSubtrees(currProp, baseNode, true); - } - propertyIt.Remove(); - } - else - { - // This is an alias to an array item and the array exists. - // Look for the aliased item. - // Then transplant or check & delete as appropriate. - XMPNode itemNode = null; - if (info.GetAliasForm().IsArrayAltText()) - { - int xdIndex = XMPNodeUtils.LookupLanguageItem(baseNode, XMPConstConstants.XDefault); - if (xdIndex != -1) - { - itemNode = baseNode.GetChild(xdIndex); - } - } - else - { - if (baseNode.HasChildren()) - { - itemNode = baseNode.GetChild(1); - } - } - if (itemNode == null) - { - TransplantArrayItemAlias(propertyIt, currProp, baseNode); - } - else - { - if (strictAliasing) - { - CompareAliasedSubtrees(currProp, itemNode, true); - } - propertyIt.Remove(); - } - } - } - } - } - currSchema.SetHasAliases(false); - } - } - - /// Moves an alias node of array form to another schema into an array - /// the property iterator of the old schema (used to delete the property) - /// the node to be moved - /// the base array for the array item - /// Forwards XMP errors - private static void TransplantArrayItemAlias(Iterator propertyIt, XMPNode childNode, XMPNode baseArray) - { - if (baseArray.GetOptions().IsArrayAltText()) - { - if (childNode.GetOptions().GetHasLanguage()) - { - throw new XMPException("Alias to x-default already has a language qualifier", XMPErrorConstants.Badxmp); - } - XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); - childNode.AddQualifier(langQual); - } - propertyIt.Remove(); - childNode.SetName(XMPConstConstants.ArrayItemName); - baseArray.AddChild(childNode); - } - - /// Fixes the GPS Timestamp in EXIF. - /// the EXIF schema node - /// Thrown if the date conversion fails. - private static void FixGPSTimeStamp(XMPNode exifSchema) - { - // Note: if dates are not found the convert-methods throws an exceptions, - // and this methods returns. - XMPNode gpsDateTime = XMPNodeUtils.FindChildNode(exifSchema, "exif:GPSTimeStamp", false); - if (gpsDateTime == null) - { - return; - } - try - { - XMPDateTime binGPSStamp; - XMPDateTime binOtherDate; - binGPSStamp = XMPUtils.ConvertToDate(gpsDateTime.GetValue()); - if (binGPSStamp.GetYear() != 0 || binGPSStamp.GetMonth() != 0 || binGPSStamp.GetDay() != 0) - { - return; - } - XMPNode otherDate = XMPNodeUtils.FindChildNode(exifSchema, "exif:DateTimeOriginal", false); - if (otherDate == null) - { - otherDate = XMPNodeUtils.FindChildNode(exifSchema, "exif:DateTimeDigitized", false); - } - binOtherDate = XMPUtils.ConvertToDate(otherDate.GetValue()); - Calendar cal = binGPSStamp.GetCalendar(); - cal.Set(CalendarEnum.Year, binOtherDate.GetYear()); - cal.Set(CalendarEnum.Month, binOtherDate.GetMonth()); - cal.Set(CalendarEnum.DayOfMonth, binOtherDate.GetDay()); - binGPSStamp = new XMPDateTimeImpl(cal); - gpsDateTime.SetValue(XMPUtils.ConvertFromDate(binGPSStamp)); - } - catch (XMPException) - { - // Don't let a missing or bad date stop other things. - return; - } - } - - /// Remove all empty schemas from the metadata tree that were generated during the rdf parsing. - /// the root of the metadata tree - private static void DeleteEmptySchemas(XMPNode tree) - { - // Delete empty schema nodes. Do this last, other cleanup can make empty - // schema. - for (Iterator it = tree.IterateChildren(); it.HasNext(); ) - { - XMPNode schema = (XMPNode)it.Next(); - if (!schema.HasChildren()) - { - it.Remove(); - } - } - } - - /// The outermost call is special. - /// - /// The outermost call is special. The names almost certainly differ. The - /// qualifiers (and hence options) will differ for an alias to the x-default - /// item of a langAlt array. - /// - /// the alias node - /// the base node of the alias - /// marks the outer call of the recursion - /// Forwards XMP errors - private static void CompareAliasedSubtrees(XMPNode aliasNode, XMPNode baseNode, bool outerCall) - { - if (!aliasNode.GetValue().Equals(baseNode.GetValue()) || aliasNode.GetChildrenLength() != baseNode.GetChildrenLength()) - { - throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); - } - if (!outerCall && (!aliasNode.GetName().Equals(baseNode.GetName()) || !aliasNode.GetOptions().Equals(baseNode.GetOptions()) || aliasNode.GetQualifierLength() != baseNode.GetQualifierLength())) - { - throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); - } - for (Iterator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.HasNext() && bn.HasNext(); ) - { - XMPNode aliasChild = (XMPNode)an.Next(); - XMPNode baseChild = (XMPNode)bn.Next(); - CompareAliasedSubtrees(aliasChild, baseChild, false); - } - for (Iterator an_1 = aliasNode.IterateQualifier(), bn_1 = baseNode.IterateQualifier(); an_1.HasNext() && bn_1.HasNext(); ) - { - XMPNode aliasQual = (XMPNode)an_1.Next(); - XMPNode baseQual = (XMPNode)bn_1.Next(); - CompareAliasedSubtrees(aliasQual, baseQual, false); - } - } - - /// - /// The initial support for WAV files mapped a legacy ID3 audio copyright - /// into a new xmpDM:copyright property. - /// - /// - /// The initial support for WAV files mapped a legacy ID3 audio copyright - /// into a new xmpDM:copyright property. This is special case code to migrate - /// that into dc:rights['x-default']. The rules: - ///
-        /// 1. If there is no dc:rights array, or an empty array -
-        /// Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright.
-        /// 2. If there is a dc:rights array but it has no x-default item -
-        /// Create an x-default item as a copy of the first item then apply rule #3.
-        /// 3. If there is a dc:rights array with an x-default item,
-        /// Look for a double linefeed in the value.
-        /// A. If no double linefeed, compare the x-default value to the xmpDM:copyright value.
-        /// A1. If they match then leave the x-default value alone.
-        /// A2. Otherwise, append a double linefeed and
-        /// the xmpDM:copyright value to the x-default value.
-        /// B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value.
-        /// B1. If they match then leave the x-default value alone.
-        /// B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value.
-        /// 4. In all cases, delete the xmpDM:copyright property.
-        /// 
- ///
- /// the metadata object - /// the "dm:copyright"-property - private static void MigrateAudioCopyright(XMPMeta xmp, XMPNode dmCopyright) - { - try - { - XMPNode dcSchema = XMPNodeUtils.FindSchemaNode(((XMPMetaImpl)xmp).GetRoot(), XMPConstConstants.NsDc, true); - string dmValue = dmCopyright.GetValue(); - string doubleLF = "\n\n"; - XMPNode dcRightsArray = XMPNodeUtils.FindChildNode(dcSchema, "dc:rights", false); - if (dcRightsArray == null || !dcRightsArray.HasChildren()) - { - // 1. No dc:rights array, create from double linefeed and xmpDM:copyright. - dmValue = doubleLF + dmValue; - xmp.SetLocalizedText(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, dmValue, null); - } - else - { - int xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); - if (xdIndex < 0) - { - // 2. No x-default item, create from the first item. - string firstValue = dcRightsArray.GetChild(1).GetValue(); - xmp.SetLocalizedText(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, firstValue, null); - xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); - } - // 3. Look for a double linefeed in the x-default value. - XMPNode defaultNode = dcRightsArray.GetChild(xdIndex); - string defaultValue = defaultNode.GetValue(); - int lfPos = defaultValue.IndexOf(doubleLF); - if (lfPos < 0) - { - // 3A. No double LF, compare whole values. - if (!dmValue.Equals(defaultValue)) - { - // 3A2. Append the xmpDM:copyright to the x-default - // item. - defaultNode.SetValue(defaultValue + doubleLF + dmValue); - } - } - else - { - // 3B. Has double LF, compare the tail. - if (!Runtime.Substring(defaultValue, lfPos + 2).Equals(dmValue)) - { - // 3B2. Replace the x-default tail. - defaultNode.SetValue(Runtime.Substring(defaultValue, 0, lfPos + 2) + dmValue); - } - } - } - // 4. Get rid of the xmpDM:copyright. - dmCopyright.GetParent().RemoveChild(dmCopyright); - } - catch (XMPException) - { - } - } - - // Don't let failures (like a bad dc:rights form) stop other - // cleanup. - /// - /// Initializes the map that contains the known arrays, that are fixed by - /// - /// . - /// - private static void InitDCArrays() - { - dcArrayForms = new Hashtable(); - // Properties supposed to be a "Bag". - PropertyOptions bagForm = new PropertyOptions(); - bagForm.SetArray(true); - dcArrayForms.Put("dc:contributor", bagForm); - dcArrayForms.Put("dc:language", bagForm); - dcArrayForms.Put("dc:publisher", bagForm); - dcArrayForms.Put("dc:relation", bagForm); - dcArrayForms.Put("dc:subject", bagForm); - dcArrayForms.Put("dc:type", bagForm); - // Properties supposed to be a "Seq". - PropertyOptions seqForm = new PropertyOptions(); - seqForm.SetArray(true); - seqForm.SetArrayOrdered(true); - dcArrayForms.Put("dc:creator", seqForm); - dcArrayForms.Put("dc:date", seqForm); - // Properties supposed to be an "Alt" in alternative-text form. - PropertyOptions altTextForm = new PropertyOptions(); - altTextForm.SetArray(true); - altTextForm.SetArrayOrdered(true); - altTextForm.SetArrayAlternate(true); - altTextForm.SetArrayAltText(true); - dcArrayForms.Put("dc:description", altTextForm); - dcArrayForms.Put("dc:rights", altTextForm); - dcArrayForms.Put("dc:title", altTextForm); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Collections; +using Com.Adobe.Xmp.Impl.Xpath; +using Com.Adobe.Xmp.Options; +using Com.Adobe.Xmp.Properties; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// Aug 18, 2006 + public class XMPNormalizer + { + /// caches the correct dc-property array forms + private static IDictionary dcArrayForms; + + static XMPNormalizer() + { + InitDCArrays(); + } + + /// Hidden constructor + private XMPNormalizer() + { + } + + // EMPTY + /// Normalizes a raw parsed XMPMeta-Object + /// the raw metadata object + /// the parsing options + /// Returns the normalized metadata object + /// Collects all severe processing errors. + internal static XMPMeta Process(XMPMetaImpl xmp, ParseOptions options) + { + XMPNode tree = xmp.GetRoot(); + TouchUpDataModel(xmp); + MoveExplicitAliases(tree, options); + TweakOldXMP(tree); + DeleteEmptySchemas(tree); + return xmp; + } + + /// + /// Tweak old XMP: Move an instance ID from rdf:about to the + /// xmpMM:InstanceID property. + /// + /// + /// Tweak old XMP: Move an instance ID from rdf:about to the + /// xmpMM:InstanceID property. An old instance ID usually looks + /// like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign + /// 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If + /// the name looks like a UUID simply move it to xmpMM:InstanceID, + /// don't worry about any existing xmpMM:InstanceID. Both will + /// only be present when a newer file with the xmpMM:InstanceID + /// property is updated by an old app that uses rdf:about. + /// + /// the root of the metadata tree + /// Thrown if tweaking fails. + private static void TweakOldXMP(XMPNode tree) + { + if (tree.GetName() != null && tree.GetName().Length >= Utils.UuidLength) + { + string nameStr = tree.GetName().ToLower(); + if (nameStr.StartsWith("uuid:")) + { + nameStr = Runtime.Substring(nameStr, 5); + } + if (Utils.CheckUUIDFormat(nameStr)) + { + // move UUID to xmpMM:InstanceID and remove it from the root node + XMPPath path = XMPPathParser.ExpandXPath(XMPConstConstants.NsXmpMm, "InstanceID"); + XMPNode idNode = XMPNodeUtils.FindNode(tree, path, true, null); + if (idNode != null) + { + idNode.SetOptions(null); + // Clobber any existing xmpMM:InstanceID. + idNode.SetValue("uuid:" + nameStr); + idNode.RemoveChildren(); + idNode.RemoveQualifiers(); + tree.SetName(null); + } + else + { + throw new XMPException("Failure creating xmpMM:InstanceID", XMPErrorConstants.Internalfailure); + } + } + } + } + + /// Visit all schemas to do general fixes and handle special cases. + /// the metadata object implementation + /// Thrown if the normalisation fails. + private static void TouchUpDataModel(XMPMetaImpl xmp) + { + // make sure the DC schema is existing, because it might be needed within the normalization + // if not touched it will be removed by removeEmptySchemas + XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), XMPConstConstants.NsDc, true); + // Do the special case fixes within each schema. + for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); ) + { + XMPNode currSchema = (XMPNode)it.Next(); + if (XMPConstConstants.NsDc.Equals(currSchema.GetName())) + { + NormalizeDCArrays(currSchema); + } + else + { + if (XMPConstConstants.NsExif.Equals(currSchema.GetName())) + { + // Do a special case fix for exif:GPSTimeStamp. + FixGPSTimeStamp(currSchema); + XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "exif:UserComment", false); + if (arrayNode != null) + { + RepairAltText(arrayNode); + } + } + else + { + if (XMPConstConstants.NsDm.Equals(currSchema.GetName())) + { + // Do a special case migration of xmpDM:copyright to + // dc:rights['x-default']. + XMPNode dmCopyright = XMPNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false); + if (dmCopyright != null) + { + MigrateAudioCopyright(xmp, dmCopyright); + } + } + else + { + if (XMPConstConstants.NsXmpRights.Equals(currSchema.GetName())) + { + XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false); + if (arrayNode != null) + { + RepairAltText(arrayNode); + } + } + } + } + } + } + } + + /// + /// Undo the denormalization performed by the XMP used in Acrobat 5.
+ /// If a Dublin Core array had only one item, it was serialized as a simple + /// property. + ///
+ /// + /// Undo the denormalization performed by the XMP used in Acrobat 5.
+ /// If a Dublin Core array had only one item, it was serialized as a simple + /// property.
+ /// The xml:lang attribute was dropped from an + /// alt-text item if the language was x-default. + ///
+ /// the DC schema node + /// Thrown if normalization fails + private static void NormalizeDCArrays(XMPNode dcSchema) + { + for (int i = 1; i <= dcSchema.GetChildrenLength(); i++) + { + XMPNode currProp = dcSchema.GetChild(i); + PropertyOptions arrayForm = (PropertyOptions)dcArrayForms.Get(currProp.GetName()); + if (arrayForm == null) + { + continue; + } + else + { + if (currProp.GetOptions().IsSimple()) + { + // create a new array and add the current property as child, + // if it was formerly simple + XMPNode newArray = new XMPNode(currProp.GetName(), arrayForm); + currProp.SetName(XMPConstConstants.ArrayItemName); + newArray.AddChild(currProp); + dcSchema.ReplaceChild(i, newArray); + // fix language alternatives + if (arrayForm.IsArrayAltText() && !currProp.GetOptions().GetHasLanguage()) + { + XMPNode newLang = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); + currProp.AddQualifier(newLang); + } + } + else + { + // clear array options and add corrected array form if it has been an array before + currProp.GetOptions().SetOption(PropertyOptions.Array | PropertyOptions.ArrayOrdered | PropertyOptions.ArrayAlternate | PropertyOptions.ArrayAltText, false); + currProp.GetOptions().MergeWith(arrayForm); + if (arrayForm.IsArrayAltText()) + { + // applying for "dc:description", "dc:rights", "dc:title" + RepairAltText(currProp); + } + } + } + } + } + + /// Make sure that the array is well-formed AltText. + /// + /// Make sure that the array is well-formed AltText. Each item must be simple + /// and have an "xml:lang" qualifier. If repairs are needed, keep simple + /// non-empty items by adding the "xml:lang" with value "x-repair". + /// + /// the property node of the array to repair. + /// Forwards unexpected exceptions. + private static void RepairAltText(XMPNode arrayNode) + { + if (arrayNode == null || !arrayNode.GetOptions().IsArray()) + { + // Already OK or not even an array. + return; + } + // fix options + arrayNode.GetOptions().SetArrayOrdered(true).SetArrayAlternate(true).SetArrayAltText(true); + for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) + { + XMPNode currChild = (XMPNode)it.Next(); + if (currChild.GetOptions().IsCompositeProperty()) + { + // Delete non-simple children. + it.Remove(); + } + else + { + if (!currChild.GetOptions().GetHasLanguage()) + { + string childValue = currChild.GetValue(); + if (childValue == null || childValue.Length == 0) + { + // Delete empty valued children that have no xml:lang. + it.Remove(); + } + else + { + // Add an xml:lang qualifier with the value "x-repair". + XMPNode repairLang = new XMPNode(XMPConstConstants.XmlLang, "x-repair", null); + currChild.AddQualifier(repairLang); + } + } + } + } + } + + /// Visit all of the top level nodes looking for aliases. + /// + /// Visit all of the top level nodes looking for aliases. If there is + /// no base, transplant the alias subtree. If there is a base and strict + /// aliasing is on, make sure the alias and base subtrees match. + /// + /// the root of the metadata tree + /// th parsing options + /// Forwards XMP errors + private static void MoveExplicitAliases(XMPNode tree, ParseOptions options) + { + if (!tree.GetHasAliases()) + { + return; + } + tree.SetHasAliases(false); + bool strictAliasing = options.GetStrictAliasing(); + for (Iterator schemaIt = tree.GetUnmodifiableChildren().Iterator(); schemaIt.HasNext(); ) + { + XMPNode currSchema = (XMPNode)schemaIt.Next(); + if (!currSchema.GetHasAliases()) + { + continue; + } + for (Iterator propertyIt = currSchema.IterateChildren(); propertyIt.HasNext(); ) + { + XMPNode currProp = (XMPNode)propertyIt.Next(); + if (!currProp.IsAlias()) + { + continue; + } + currProp.SetAlias(false); + // Find the base path, look for the base schema and root node. + XMPAliasInfo info = XMPMetaFactory.GetSchemaRegistry().FindAlias(currProp.GetName()); + if (info != null) + { + // find or create schema + XMPNode baseSchema = XMPNodeUtils.FindSchemaNode(tree, info.GetNamespace(), null, true); + baseSchema.SetImplicit(false); + XMPNode baseNode = XMPNodeUtils.FindChildNode(baseSchema, info.GetPrefix() + info.GetPropName(), false); + if (baseNode == null) + { + if (info.GetAliasForm().IsSimple()) + { + // A top-to-top alias, transplant the property. + // change the alias property name to the base name + string qname = info.GetPrefix() + info.GetPropName(); + currProp.SetName(qname); + baseSchema.AddChild(currProp); + // remove the alias property + propertyIt.Remove(); + } + else + { + // An alias to an array item, + // create the array and transplant the property. + baseNode = new XMPNode(info.GetPrefix() + info.GetPropName(), info.GetAliasForm().ToPropertyOptions()); + baseSchema.AddChild(baseNode); + TransplantArrayItemAlias(propertyIt, currProp, baseNode); + } + } + else + { + if (info.GetAliasForm().IsSimple()) + { + // The base node does exist and this is a top-to-top alias. + // Check for conflicts if strict aliasing is on. + // Remove and delete the alias subtree. + if (strictAliasing) + { + CompareAliasedSubtrees(currProp, baseNode, true); + } + propertyIt.Remove(); + } + else + { + // This is an alias to an array item and the array exists. + // Look for the aliased item. + // Then transplant or check & delete as appropriate. + XMPNode itemNode = null; + if (info.GetAliasForm().IsArrayAltText()) + { + int xdIndex = XMPNodeUtils.LookupLanguageItem(baseNode, XMPConstConstants.XDefault); + if (xdIndex != -1) + { + itemNode = baseNode.GetChild(xdIndex); + } + } + else + { + if (baseNode.HasChildren()) + { + itemNode = baseNode.GetChild(1); + } + } + if (itemNode == null) + { + TransplantArrayItemAlias(propertyIt, currProp, baseNode); + } + else + { + if (strictAliasing) + { + CompareAliasedSubtrees(currProp, itemNode, true); + } + propertyIt.Remove(); + } + } + } + } + } + currSchema.SetHasAliases(false); + } + } + + /// Moves an alias node of array form to another schema into an array + /// the property iterator of the old schema (used to delete the property) + /// the node to be moved + /// the base array for the array item + /// Forwards XMP errors + private static void TransplantArrayItemAlias(Iterator propertyIt, XMPNode childNode, XMPNode baseArray) + { + if (baseArray.GetOptions().IsArrayAltText()) + { + if (childNode.GetOptions().GetHasLanguage()) + { + throw new XMPException("Alias to x-default already has a language qualifier", XMPErrorConstants.Badxmp); + } + XMPNode langQual = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); + childNode.AddQualifier(langQual); + } + propertyIt.Remove(); + childNode.SetName(XMPConstConstants.ArrayItemName); + baseArray.AddChild(childNode); + } + + /// Fixes the GPS Timestamp in EXIF. + /// the EXIF schema node + /// Thrown if the date conversion fails. + private static void FixGPSTimeStamp(XMPNode exifSchema) + { + // Note: if dates are not found the convert-methods throws an exceptions, + // and this methods returns. + XMPNode gpsDateTime = XMPNodeUtils.FindChildNode(exifSchema, "exif:GPSTimeStamp", false); + if (gpsDateTime == null) + { + return; + } + try + { + XMPDateTime binGPSStamp; + XMPDateTime binOtherDate; + binGPSStamp = XMPUtils.ConvertToDate(gpsDateTime.GetValue()); + if (binGPSStamp.GetYear() != 0 || binGPSStamp.GetMonth() != 0 || binGPSStamp.GetDay() != 0) + { + return; + } + XMPNode otherDate = XMPNodeUtils.FindChildNode(exifSchema, "exif:DateTimeOriginal", false); + if (otherDate == null) + { + otherDate = XMPNodeUtils.FindChildNode(exifSchema, "exif:DateTimeDigitized", false); + } + binOtherDate = XMPUtils.ConvertToDate(otherDate.GetValue()); + Calendar cal = binGPSStamp.GetCalendar(); + cal.Set(CalendarEnum.Year, binOtherDate.GetYear()); + cal.Set(CalendarEnum.Month, binOtherDate.GetMonth()); + cal.Set(CalendarEnum.DayOfMonth, binOtherDate.GetDay()); + binGPSStamp = new XMPDateTimeImpl(cal); + gpsDateTime.SetValue(XMPUtils.ConvertFromDate(binGPSStamp)); + } + catch (XMPException) + { + // Don't let a missing or bad date stop other things. + return; + } + } + + /// Remove all empty schemas from the metadata tree that were generated during the rdf parsing. + /// the root of the metadata tree + private static void DeleteEmptySchemas(XMPNode tree) + { + // Delete empty schema nodes. Do this last, other cleanup can make empty + // schema. + for (Iterator it = tree.IterateChildren(); it.HasNext(); ) + { + XMPNode schema = (XMPNode)it.Next(); + if (!schema.HasChildren()) + { + it.Remove(); + } + } + } + + /// The outermost call is special. + /// + /// The outermost call is special. The names almost certainly differ. The + /// qualifiers (and hence options) will differ for an alias to the x-default + /// item of a langAlt array. + /// + /// the alias node + /// the base node of the alias + /// marks the outer call of the recursion + /// Forwards XMP errors + private static void CompareAliasedSubtrees(XMPNode aliasNode, XMPNode baseNode, bool outerCall) + { + if (!aliasNode.GetValue().Equals(baseNode.GetValue()) || aliasNode.GetChildrenLength() != baseNode.GetChildrenLength()) + { + throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); + } + if (!outerCall && (!aliasNode.GetName().Equals(baseNode.GetName()) || !aliasNode.GetOptions().Equals(baseNode.GetOptions()) || aliasNode.GetQualifierLength() != baseNode.GetQualifierLength())) + { + throw new XMPException("Mismatch between alias and base nodes", XMPErrorConstants.Badxmp); + } + for (Iterator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.HasNext() && bn.HasNext(); ) + { + XMPNode aliasChild = (XMPNode)an.Next(); + XMPNode baseChild = (XMPNode)bn.Next(); + CompareAliasedSubtrees(aliasChild, baseChild, false); + } + for (Iterator an_1 = aliasNode.IterateQualifier(), bn_1 = baseNode.IterateQualifier(); an_1.HasNext() && bn_1.HasNext(); ) + { + XMPNode aliasQual = (XMPNode)an_1.Next(); + XMPNode baseQual = (XMPNode)bn_1.Next(); + CompareAliasedSubtrees(aliasQual, baseQual, false); + } + } + + /// + /// The initial support for WAV files mapped a legacy ID3 audio copyright + /// into a new xmpDM:copyright property. + /// + /// + /// The initial support for WAV files mapped a legacy ID3 audio copyright + /// into a new xmpDM:copyright property. This is special case code to migrate + /// that into dc:rights['x-default']. The rules: + ///
+        /// 1. If there is no dc:rights array, or an empty array -
+        /// Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright.
+        /// 2. If there is a dc:rights array but it has no x-default item -
+        /// Create an x-default item as a copy of the first item then apply rule #3.
+        /// 3. If there is a dc:rights array with an x-default item,
+        /// Look for a double linefeed in the value.
+        /// A. If no double linefeed, compare the x-default value to the xmpDM:copyright value.
+        /// A1. If they match then leave the x-default value alone.
+        /// A2. Otherwise, append a double linefeed and
+        /// the xmpDM:copyright value to the x-default value.
+        /// B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value.
+        /// B1. If they match then leave the x-default value alone.
+        /// B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value.
+        /// 4. In all cases, delete the xmpDM:copyright property.
+        /// 
+ ///
+ /// the metadata object + /// the "dm:copyright"-property + private static void MigrateAudioCopyright(XMPMeta xmp, XMPNode dmCopyright) + { + try + { + XMPNode dcSchema = XMPNodeUtils.FindSchemaNode(((XMPMetaImpl)xmp).GetRoot(), XMPConstConstants.NsDc, true); + string dmValue = dmCopyright.GetValue(); + string doubleLF = "\n\n"; + XMPNode dcRightsArray = XMPNodeUtils.FindChildNode(dcSchema, "dc:rights", false); + if (dcRightsArray == null || !dcRightsArray.HasChildren()) + { + // 1. No dc:rights array, create from double linefeed and xmpDM:copyright. + dmValue = doubleLF + dmValue; + xmp.SetLocalizedText(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, dmValue, null); + } + else + { + int xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); + if (xdIndex < 0) + { + // 2. No x-default item, create from the first item. + string firstValue = dcRightsArray.GetChild(1).GetValue(); + xmp.SetLocalizedText(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, firstValue, null); + xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); + } + // 3. Look for a double linefeed in the x-default value. + XMPNode defaultNode = dcRightsArray.GetChild(xdIndex); + string defaultValue = defaultNode.GetValue(); + int lfPos = defaultValue.IndexOf(doubleLF); + if (lfPos < 0) + { + // 3A. No double LF, compare whole values. + if (!dmValue.Equals(defaultValue)) + { + // 3A2. Append the xmpDM:copyright to the x-default + // item. + defaultNode.SetValue(defaultValue + doubleLF + dmValue); + } + } + else + { + // 3B. Has double LF, compare the tail. + if (!Runtime.Substring(defaultValue, lfPos + 2).Equals(dmValue)) + { + // 3B2. Replace the x-default tail. + defaultNode.SetValue(Runtime.Substring(defaultValue, 0, lfPos + 2) + dmValue); + } + } + } + // 4. Get rid of the xmpDM:copyright. + dmCopyright.GetParent().RemoveChild(dmCopyright); + } + catch (XMPException) + { + } + } + + // Don't let failures (like a bad dc:rights form) stop other + // cleanup. + /// + /// Initializes the map that contains the known arrays, that are fixed by + /// + /// . + /// + private static void InitDCArrays() + { + dcArrayForms = new Hashtable(); + // Properties supposed to be a "Bag". + PropertyOptions bagForm = new PropertyOptions(); + bagForm.SetArray(true); + dcArrayForms.Put("dc:contributor", bagForm); + dcArrayForms.Put("dc:language", bagForm); + dcArrayForms.Put("dc:publisher", bagForm); + dcArrayForms.Put("dc:relation", bagForm); + dcArrayForms.Put("dc:subject", bagForm); + dcArrayForms.Put("dc:type", bagForm); + // Properties supposed to be a "Seq". + PropertyOptions seqForm = new PropertyOptions(); + seqForm.SetArray(true); + seqForm.SetArrayOrdered(true); + dcArrayForms.Put("dc:creator", seqForm); + dcArrayForms.Put("dc:date", seqForm); + // Properties supposed to be an "Alt" in alternative-text form. + PropertyOptions altTextForm = new PropertyOptions(); + altTextForm.SetArray(true); + altTextForm.SetArrayOrdered(true); + altTextForm.SetArrayAlternate(true); + altTextForm.SetArrayAltText(true); + dcArrayForms.Put("dc:description", altTextForm); + dcArrayForms.Put("dc:rights", altTextForm); + dcArrayForms.Put("dc:title", altTextForm); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSchemaRegistryImpl.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSchemaRegistryImpl.cs index 9a0db90cb..705a4c348 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSchemaRegistryImpl.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSchemaRegistryImpl.cs @@ -1,475 +1,475 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Collections; -using Com.Adobe.Xmp.Options; -using Com.Adobe.Xmp.Properties; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. - /// - /// The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. There - /// is only one single instance used by the toolkit. - /// - /// 27.01.2006 - public sealed class XMPSchemaRegistryImpl : XMPSchemaRegistry, XMPConst - { - /// a map from a namespace URI to its registered prefix - private IDictionary namespaceToPrefixMap = new Hashtable(); - - /// a map from a prefix to the associated namespace URI - private IDictionary prefixToNamespaceMap = new Hashtable(); - - /// a map of all registered aliases. - /// - /// a map of all registered aliases. - /// The map is a relationship from a qname to an XMPAliasInfo-object. - /// - private IDictionary aliasMap = new Hashtable(); - - /// The pattern that must not be contained in simple properties - private Pattern p = Pattern.Compile("[/*?\\[\\]]"); - - /// - /// Performs the initialisation of the registry with the default namespaces, aliases and global - /// options. - /// - public XMPSchemaRegistryImpl() - { - try - { - RegisterStandardNamespaces(); - RegisterStandardAliases(); - } - catch (XMPException) - { - throw new RuntimeException("The XMPSchemaRegistry cannot be initialized!"); - } - } - - // --------------------------------------------------------------------------------------------- - // Namespace Functions - /// - /// - public string RegisterNamespace(string namespaceURI, string suggestedPrefix) - { - lock (this) - { - ParameterAsserts.AssertSchemaNS(namespaceURI); - ParameterAsserts.AssertPrefix(suggestedPrefix); - if (suggestedPrefix[suggestedPrefix.Length - 1] != ':') - { - suggestedPrefix += ':'; - } - if (!Utils.IsXMLNameNS(Runtime.Substring(suggestedPrefix, 0, suggestedPrefix.Length - 1))) - { - throw new XMPException("The prefix is a bad XML name", XMPErrorConstants.Badxml); - } - string registeredPrefix = (string)namespaceToPrefixMap.Get(namespaceURI); - string registeredNS = (string)prefixToNamespaceMap.Get(suggestedPrefix); - if (registeredPrefix != null) - { - // Return the actual prefix - return registeredPrefix; - } - else - { - if (registeredNS != null) - { - // the namespace is new, but the prefix is already engaged, - // we generate a new prefix out of the suggested - string generatedPrefix = suggestedPrefix; - for (int i = 1; prefixToNamespaceMap.ContainsKey(generatedPrefix); i++) - { - generatedPrefix = Runtime.Substring(suggestedPrefix, 0, suggestedPrefix.Length - 1) + "_" + i + "_:"; - } - suggestedPrefix = generatedPrefix; - } - prefixToNamespaceMap.Put(suggestedPrefix, namespaceURI); - namespaceToPrefixMap.Put(namespaceURI, suggestedPrefix); - // Return the suggested prefix - return suggestedPrefix; - } - } - } - - /// - public void DeleteNamespace(string namespaceURI) - { - lock (this) - { - string prefixToDelete = GetNamespacePrefix(namespaceURI); - if (prefixToDelete != null) - { - Collections.Remove(namespaceToPrefixMap, namespaceURI); - Collections.Remove(prefixToNamespaceMap, prefixToDelete); - } - } - } - - /// - public string GetNamespacePrefix(string namespaceURI) - { - lock (this) - { - return (string)namespaceToPrefixMap.Get(namespaceURI); - } - } - - /// - public string GetNamespaceURI(string namespacePrefix) - { - lock (this) - { - if (namespacePrefix != null && !namespacePrefix.EndsWith(":")) - { - namespacePrefix += ":"; - } - return (string)prefixToNamespaceMap.Get(namespacePrefix); - } - } - - /// - public IDictionary GetNamespaces() - { - lock (this) - { - return Collections.UnmodifiableMap(new SortedList(namespaceToPrefixMap)); - } - } - - /// - public IDictionary GetPrefixes() - { - lock (this) - { - return Collections.UnmodifiableMap(new SortedList(prefixToNamespaceMap)); - } - } - - /// - /// Register the standard namespaces of schemas and types that are included in the XMP - /// Specification and some other Adobe private namespaces. - /// - /// - /// Register the standard namespaces of schemas and types that are included in the XMP - /// Specification and some other Adobe private namespaces. - /// Note: This method is not lock because only called by the constructor. - /// - /// Forwards processing exceptions - private void RegisterStandardNamespaces() - { - // register standard namespaces - RegisterNamespace(XMPConstConstants.NsXml, "xml"); - RegisterNamespace(XMPConstConstants.NsRdf, "rdf"); - RegisterNamespace(XMPConstConstants.NsDc, "dc"); - RegisterNamespace(XMPConstConstants.NsIptccore, "Iptc4xmpCore"); - RegisterNamespace(XMPConstConstants.NsIptcext, "Iptc4xmpExt"); - RegisterNamespace(XMPConstConstants.NsDicom, "DICOM"); - RegisterNamespace(XMPConstConstants.NsPlus, "plus"); - // register Adobe standard namespaces - RegisterNamespace(XMPConstConstants.NsX, "x"); - RegisterNamespace(XMPConstConstants.NsIx, "iX"); - RegisterNamespace(XMPConstConstants.NsXmp, "xmp"); - RegisterNamespace(XMPConstConstants.NsXmpRights, "xmpRights"); - RegisterNamespace(XMPConstConstants.NsXmpMm, "xmpMM"); - RegisterNamespace(XMPConstConstants.NsXmpBj, "xmpBJ"); - RegisterNamespace(XMPConstConstants.NsXmpNote, "xmpNote"); - RegisterNamespace(XMPConstConstants.NsPdf, "pdf"); - RegisterNamespace(XMPConstConstants.NsPdfx, "pdfx"); - RegisterNamespace(XMPConstConstants.NsPdfxId, "pdfxid"); - RegisterNamespace(XMPConstConstants.NsPdfaSchema, "pdfaSchema"); - RegisterNamespace(XMPConstConstants.NsPdfaProperty, "pdfaProperty"); - RegisterNamespace(XMPConstConstants.NsPdfaType, "pdfaType"); - RegisterNamespace(XMPConstConstants.NsPdfaField, "pdfaField"); - RegisterNamespace(XMPConstConstants.NsPdfaId, "pdfaid"); - RegisterNamespace(XMPConstConstants.NsPdfaExtension, "pdfaExtension"); - RegisterNamespace(XMPConstConstants.NsPhotoshop, "photoshop"); - RegisterNamespace(XMPConstConstants.NsPsalbum, "album"); - RegisterNamespace(XMPConstConstants.NsExif, "exif"); - RegisterNamespace(XMPConstConstants.NsExifx, "exifEX"); - RegisterNamespace(XMPConstConstants.NsExifAux, "aux"); - RegisterNamespace(XMPConstConstants.NsTiff, "tiff"); - RegisterNamespace(XMPConstConstants.NsPng, "png"); - RegisterNamespace(XMPConstConstants.NsJpeg, "jpeg"); - RegisterNamespace(XMPConstConstants.NsJp2k, "jp2k"); - RegisterNamespace(XMPConstConstants.NsCameraraw, "crs"); - RegisterNamespace(XMPConstConstants.NsAdobestockphoto, "bmsp"); - RegisterNamespace(XMPConstConstants.NsCreatorAtom, "creatorAtom"); - RegisterNamespace(XMPConstConstants.NsAsf, "asf"); - RegisterNamespace(XMPConstConstants.NsWav, "wav"); - RegisterNamespace(XMPConstConstants.NsBwf, "bext"); - RegisterNamespace(XMPConstConstants.NsRiffinfo, "riffinfo"); - RegisterNamespace(XMPConstConstants.NsScript, "xmpScript"); - RegisterNamespace(XMPConstConstants.NsTxmp, "txmp"); - RegisterNamespace(XMPConstConstants.NsSwf, "swf"); - // register Adobe private namespaces - RegisterNamespace(XMPConstConstants.NsDm, "xmpDM"); - RegisterNamespace(XMPConstConstants.NsTransient, "xmpx"); - // register Adobe standard type namespaces - RegisterNamespace(XMPConstConstants.TypeText, "xmpT"); - RegisterNamespace(XMPConstConstants.TypePagedfile, "xmpTPg"); - RegisterNamespace(XMPConstConstants.TypeGraphics, "xmpG"); - RegisterNamespace(XMPConstConstants.TypeImage, "xmpGImg"); - RegisterNamespace(XMPConstConstants.TypeFont, "stFnt"); - RegisterNamespace(XMPConstConstants.TypeDimensions, "stDim"); - RegisterNamespace(XMPConstConstants.TypeResourceevent, "stEvt"); - RegisterNamespace(XMPConstConstants.TypeResourceref, "stRef"); - RegisterNamespace(XMPConstConstants.TypeStVersion, "stVer"); - RegisterNamespace(XMPConstConstants.TypeStJob, "stJob"); - RegisterNamespace(XMPConstConstants.TypeManifestitem, "stMfs"); - RegisterNamespace(XMPConstConstants.TypeIdentifierqual, "xmpidq"); - } - - // --------------------------------------------------------------------------------------------- - // Alias Functions - /// - public XMPAliasInfo ResolveAlias(string aliasNS, string aliasProp) - { - lock (this) - { - string aliasPrefix = GetNamespacePrefix(aliasNS); - if (aliasPrefix == null) - { - return null; - } - return (XMPAliasInfo)aliasMap.Get(aliasPrefix + aliasProp); - } - } - - /// - public XMPAliasInfo FindAlias(string qname) - { - lock (this) - { - return (XMPAliasInfo)aliasMap.Get(qname); - } - } - - /// - public XMPAliasInfo[] FindAliases(string aliasNS) - { - lock (this) - { - string prefix = GetNamespacePrefix(aliasNS); - IList result = new ArrayList(); - if (prefix != null) - { - for (Iterator it = aliasMap.Keys.Iterator(); it.HasNext(); ) - { - string qname = (string)it.Next(); - if (qname.StartsWith(prefix)) - { - result.Add(FindAlias(qname)); - } - } - } - return (XMPAliasInfo[])Collections.ToArray(result, new XMPAliasInfo[result.Count]); - } - } - - /// Associates an alias name with an actual name. - /// - /// Associates an alias name with an actual name. - ///

- /// Define a alias mapping from one namespace/property to another. Both - /// property names must be simple names. An alias can be a direct mapping, - /// where the alias and actual have the same data type. It is also possible - /// to map a simple alias to an item in an array. This can either be to the - /// first item in the array, or to the 'x-default' item in an alt-text array. - /// Multiple alias names may map to the same actual, as long as the forms - /// match. It is a no-op to reregister an alias in an identical fashion. - /// Note: This method is not locking because only called by registerStandardAliases - /// which is only called by the constructor. - /// Note2: The method is only package-private so that it can be tested with unittests - /// - /// - /// The namespace URI for the alias. Must not be null or the empty - /// string. - /// - /// - /// The name of the alias. Must be a simple name, not null or the - /// empty string and not a general path expression. - /// - /// - /// The namespace URI for the actual. Must not be null or the - /// empty string. - /// - /// - /// The name of the actual. Must be a simple name, not null or the - /// empty string and not a general path expression. - /// - /// - /// Provides options for aliases for simple aliases to array - /// items. This is needed to know what kind of array to create if - /// set for the first time via the simple alias. Pass - /// XMP_NoOptions, the default value, for all - /// direct aliases regardless of whether the actual data type is - /// an array or not (see - /// - /// ). - /// - /// for inconsistant aliases. - internal void RegisterAlias(string aliasNS, string aliasProp, string actualNS, string actualProp, AliasOptions aliasForm) - { - lock (this) - { - ParameterAsserts.AssertSchemaNS(aliasNS); - ParameterAsserts.AssertPropName(aliasProp); - ParameterAsserts.AssertSchemaNS(actualNS); - ParameterAsserts.AssertPropName(actualProp); - // Fix the alias options - AliasOptions aliasOpts = aliasForm != null ? new AliasOptions(XMPNodeUtils.VerifySetOptions(aliasForm.ToPropertyOptions(), null).GetOptions()) : new AliasOptions(); - if (p.Matcher(aliasProp).Find() || p.Matcher(actualProp).Find()) - { - throw new XMPException("Alias and actual property names must be simple", XMPErrorConstants.Badxpath); - } - // check if both namespaces are registered - string aliasPrefix = GetNamespacePrefix(aliasNS); - string actualPrefix = GetNamespacePrefix(actualNS); - if (aliasPrefix == null) - { - throw new XMPException("Alias namespace is not registered", XMPErrorConstants.Badschema); - } - else - { - if (actualPrefix == null) - { - throw new XMPException("Actual namespace is not registered", XMPErrorConstants.Badschema); - } - } - string key = aliasPrefix + aliasProp; - // check if alias is already existing - if (aliasMap.ContainsKey(key)) - { - throw new XMPException("Alias is already existing", XMPErrorConstants.Badparam); - } - else - { - if (aliasMap.ContainsKey(actualPrefix + actualProp)) - { - throw new XMPException("Actual property is already an alias, use the base property", XMPErrorConstants.Badparam); - } - } - XMPAliasInfo aliasInfo = new _XMPAliasInfo_390(actualNS, actualPrefix, actualProp, aliasOpts); - aliasMap.Put(key, aliasInfo); - } - } - - private sealed class _XMPAliasInfo_390 : XMPAliasInfo - { - public _XMPAliasInfo_390(string actualNS, string actualPrefix, string actualProp, AliasOptions aliasOpts) - { - this.actualNS = actualNS; - this.actualPrefix = actualPrefix; - this.actualProp = actualProp; - this.aliasOpts = aliasOpts; - } - - /// - public string GetNamespace() - { - return actualNS; - } - - /// - public string GetPrefix() - { - return actualPrefix; - } - - /// - public string GetPropName() - { - return actualProp; - } - - /// - public AliasOptions GetAliasForm() - { - return aliasOpts; - } - - public override string ToString() - { - return actualPrefix + actualProp + " NS(" + actualNS + "), FORM (" + this.GetAliasForm() + ")"; - } - - private readonly string actualNS; - - private readonly string actualPrefix; - - private readonly string actualProp; - - private readonly AliasOptions aliasOpts; - } - - /// - public IDictionary GetAliases() - { - lock (this) - { - return Collections.UnmodifiableMap(new SortedList(aliasMap)); - } - } - - ///

Register the standard aliases. - /// - /// Register the standard aliases. - /// Note: This method is not lock because only called by the constructor. - /// - /// If the registrations of at least one alias fails. - private void RegisterStandardAliases() - { - AliasOptions aliasToArrayOrdered = new AliasOptions().SetArrayOrdered(true); - AliasOptions aliasToArrayAltText = new AliasOptions().SetArrayAltText(true); - // Aliases from XMP to DC. - RegisterAlias(XMPConstConstants.NsXmp, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); - RegisterAlias(XMPConstConstants.NsXmp, "Authors", XMPConstConstants.NsDc, "creator", null); - RegisterAlias(XMPConstConstants.NsXmp, "Description", XMPConstConstants.NsDc, "description", null); - RegisterAlias(XMPConstConstants.NsXmp, "Format", XMPConstConstants.NsDc, "format", null); - RegisterAlias(XMPConstConstants.NsXmp, "Keywords", XMPConstConstants.NsDc, "subject", null); - RegisterAlias(XMPConstConstants.NsXmp, "Locale", XMPConstConstants.NsDc, "language", null); - RegisterAlias(XMPConstConstants.NsXmp, "Title", XMPConstConstants.NsDc, "title", null); - RegisterAlias(XMPConstConstants.NsXmpRights, "Copyright", XMPConstConstants.NsDc, "rights", null); - // Aliases from PDF to DC and XMP. - RegisterAlias(XMPConstConstants.NsPdf, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); - RegisterAlias(XMPConstConstants.NsPdf, "BaseURL", XMPConstConstants.NsXmp, "BaseURL", null); - RegisterAlias(XMPConstConstants.NsPdf, "CreationDate", XMPConstConstants.NsXmp, "CreateDate", null); - RegisterAlias(XMPConstConstants.NsPdf, "Creator", XMPConstConstants.NsXmp, "CreatorTool", null); - RegisterAlias(XMPConstConstants.NsPdf, "ModDate", XMPConstConstants.NsXmp, "ModifyDate", null); - RegisterAlias(XMPConstConstants.NsPdf, "Subject", XMPConstConstants.NsDc, "description", aliasToArrayAltText); - RegisterAlias(XMPConstConstants.NsPdf, "Title", XMPConstConstants.NsDc, "title", aliasToArrayAltText); - // Aliases from PHOTOSHOP to DC and XMP. - RegisterAlias(XMPConstConstants.NsPhotoshop, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); - RegisterAlias(XMPConstConstants.NsPhotoshop, "Caption", XMPConstConstants.NsDc, "description", aliasToArrayAltText); - RegisterAlias(XMPConstConstants.NsPhotoshop, "Copyright", XMPConstConstants.NsDc, "rights", aliasToArrayAltText); - RegisterAlias(XMPConstConstants.NsPhotoshop, "Keywords", XMPConstConstants.NsDc, "subject", null); - RegisterAlias(XMPConstConstants.NsPhotoshop, "Marked", XMPConstConstants.NsXmpRights, "Marked", null); - RegisterAlias(XMPConstConstants.NsPhotoshop, "Title", XMPConstConstants.NsDc, "title", aliasToArrayAltText); - RegisterAlias(XMPConstConstants.NsPhotoshop, "WebStatement", XMPConstConstants.NsXmpRights, "WebStatement", null); - // Aliases from TIFF and EXIF to DC and XMP. - RegisterAlias(XMPConstConstants.NsTiff, "Artist", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); - RegisterAlias(XMPConstConstants.NsTiff, "Copyright", XMPConstConstants.NsDc, "rights", null); - RegisterAlias(XMPConstConstants.NsTiff, "DateTime", XMPConstConstants.NsXmp, "ModifyDate", null); - RegisterAlias(XMPConstConstants.NsTiff, "ImageDescription", XMPConstConstants.NsDc, "description", null); - RegisterAlias(XMPConstConstants.NsTiff, "Software", XMPConstConstants.NsXmp, "CreatorTool", null); - // Aliases from PNG (Acrobat ImageCapture) to DC and XMP. - RegisterAlias(XMPConstConstants.NsPng, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); - RegisterAlias(XMPConstConstants.NsPng, "Copyright", XMPConstConstants.NsDc, "rights", aliasToArrayAltText); - RegisterAlias(XMPConstConstants.NsPng, "CreationTime", XMPConstConstants.NsXmp, "CreateDate", null); - RegisterAlias(XMPConstConstants.NsPng, "Description", XMPConstConstants.NsDc, "description", aliasToArrayAltText); - RegisterAlias(XMPConstConstants.NsPng, "ModificationTime", XMPConstConstants.NsXmp, "ModifyDate", null); - RegisterAlias(XMPConstConstants.NsPng, "Software", XMPConstConstants.NsXmp, "CreatorTool", null); - RegisterAlias(XMPConstConstants.NsPng, "Title", XMPConstConstants.NsDc, "title", aliasToArrayAltText); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Collections; +using Com.Adobe.Xmp.Options; +using Com.Adobe.Xmp.Properties; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. + /// + /// The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. There + /// is only one single instance used by the toolkit. + /// + /// 27.01.2006 + public sealed class XMPSchemaRegistryImpl : XMPSchemaRegistry, XMPConst + { + /// a map from a namespace URI to its registered prefix + private IDictionary namespaceToPrefixMap = new Hashtable(); + + /// a map from a prefix to the associated namespace URI + private IDictionary prefixToNamespaceMap = new Hashtable(); + + /// a map of all registered aliases. + /// + /// a map of all registered aliases. + /// The map is a relationship from a qname to an XMPAliasInfo-object. + /// + private IDictionary aliasMap = new Hashtable(); + + /// The pattern that must not be contained in simple properties + private Pattern p = Pattern.Compile("[/*?\\[\\]]"); + + /// + /// Performs the initialisation of the registry with the default namespaces, aliases and global + /// options. + /// + public XMPSchemaRegistryImpl() + { + try + { + RegisterStandardNamespaces(); + RegisterStandardAliases(); + } + catch (XMPException) + { + throw new RuntimeException("The XMPSchemaRegistry cannot be initialized!"); + } + } + + // --------------------------------------------------------------------------------------------- + // Namespace Functions + /// + /// + public string RegisterNamespace(string namespaceURI, string suggestedPrefix) + { + lock (this) + { + ParameterAsserts.AssertSchemaNS(namespaceURI); + ParameterAsserts.AssertPrefix(suggestedPrefix); + if (suggestedPrefix[suggestedPrefix.Length - 1] != ':') + { + suggestedPrefix += ':'; + } + if (!Utils.IsXMLNameNS(Runtime.Substring(suggestedPrefix, 0, suggestedPrefix.Length - 1))) + { + throw new XMPException("The prefix is a bad XML name", XMPErrorConstants.Badxml); + } + string registeredPrefix = (string)namespaceToPrefixMap.Get(namespaceURI); + string registeredNS = (string)prefixToNamespaceMap.Get(suggestedPrefix); + if (registeredPrefix != null) + { + // Return the actual prefix + return registeredPrefix; + } + else + { + if (registeredNS != null) + { + // the namespace is new, but the prefix is already engaged, + // we generate a new prefix out of the suggested + string generatedPrefix = suggestedPrefix; + for (int i = 1; prefixToNamespaceMap.ContainsKey(generatedPrefix); i++) + { + generatedPrefix = Runtime.Substring(suggestedPrefix, 0, suggestedPrefix.Length - 1) + "_" + i + "_:"; + } + suggestedPrefix = generatedPrefix; + } + prefixToNamespaceMap.Put(suggestedPrefix, namespaceURI); + namespaceToPrefixMap.Put(namespaceURI, suggestedPrefix); + // Return the suggested prefix + return suggestedPrefix; + } + } + } + + /// + public void DeleteNamespace(string namespaceURI) + { + lock (this) + { + string prefixToDelete = GetNamespacePrefix(namespaceURI); + if (prefixToDelete != null) + { + Collections.Remove(namespaceToPrefixMap, namespaceURI); + Collections.Remove(prefixToNamespaceMap, prefixToDelete); + } + } + } + + /// + public string GetNamespacePrefix(string namespaceURI) + { + lock (this) + { + return (string)namespaceToPrefixMap.Get(namespaceURI); + } + } + + /// + public string GetNamespaceURI(string namespacePrefix) + { + lock (this) + { + if (namespacePrefix != null && !namespacePrefix.EndsWith(":")) + { + namespacePrefix += ":"; + } + return (string)prefixToNamespaceMap.Get(namespacePrefix); + } + } + + /// + public IDictionary GetNamespaces() + { + lock (this) + { + return Collections.UnmodifiableMap(new SortedList(namespaceToPrefixMap)); + } + } + + /// + public IDictionary GetPrefixes() + { + lock (this) + { + return Collections.UnmodifiableMap(new SortedList(prefixToNamespaceMap)); + } + } + + /// + /// Register the standard namespaces of schemas and types that are included in the XMP + /// Specification and some other Adobe private namespaces. + /// + /// + /// Register the standard namespaces of schemas and types that are included in the XMP + /// Specification and some other Adobe private namespaces. + /// Note: This method is not lock because only called by the constructor. + /// + /// Forwards processing exceptions + private void RegisterStandardNamespaces() + { + // register standard namespaces + RegisterNamespace(XMPConstConstants.NsXml, "xml"); + RegisterNamespace(XMPConstConstants.NsRdf, "rdf"); + RegisterNamespace(XMPConstConstants.NsDc, "dc"); + RegisterNamespace(XMPConstConstants.NsIptccore, "Iptc4xmpCore"); + RegisterNamespace(XMPConstConstants.NsIptcext, "Iptc4xmpExt"); + RegisterNamespace(XMPConstConstants.NsDicom, "DICOM"); + RegisterNamespace(XMPConstConstants.NsPlus, "plus"); + // register Adobe standard namespaces + RegisterNamespace(XMPConstConstants.NsX, "x"); + RegisterNamespace(XMPConstConstants.NsIx, "iX"); + RegisterNamespace(XMPConstConstants.NsXmp, "xmp"); + RegisterNamespace(XMPConstConstants.NsXmpRights, "xmpRights"); + RegisterNamespace(XMPConstConstants.NsXmpMm, "xmpMM"); + RegisterNamespace(XMPConstConstants.NsXmpBj, "xmpBJ"); + RegisterNamespace(XMPConstConstants.NsXmpNote, "xmpNote"); + RegisterNamespace(XMPConstConstants.NsPdf, "pdf"); + RegisterNamespace(XMPConstConstants.NsPdfx, "pdfx"); + RegisterNamespace(XMPConstConstants.NsPdfxId, "pdfxid"); + RegisterNamespace(XMPConstConstants.NsPdfaSchema, "pdfaSchema"); + RegisterNamespace(XMPConstConstants.NsPdfaProperty, "pdfaProperty"); + RegisterNamespace(XMPConstConstants.NsPdfaType, "pdfaType"); + RegisterNamespace(XMPConstConstants.NsPdfaField, "pdfaField"); + RegisterNamespace(XMPConstConstants.NsPdfaId, "pdfaid"); + RegisterNamespace(XMPConstConstants.NsPdfaExtension, "pdfaExtension"); + RegisterNamespace(XMPConstConstants.NsPhotoshop, "photoshop"); + RegisterNamespace(XMPConstConstants.NsPsalbum, "album"); + RegisterNamespace(XMPConstConstants.NsExif, "exif"); + RegisterNamespace(XMPConstConstants.NsExifx, "exifEX"); + RegisterNamespace(XMPConstConstants.NsExifAux, "aux"); + RegisterNamespace(XMPConstConstants.NsTiff, "tiff"); + RegisterNamespace(XMPConstConstants.NsPng, "png"); + RegisterNamespace(XMPConstConstants.NsJpeg, "jpeg"); + RegisterNamespace(XMPConstConstants.NsJp2k, "jp2k"); + RegisterNamespace(XMPConstConstants.NsCameraraw, "crs"); + RegisterNamespace(XMPConstConstants.NsAdobestockphoto, "bmsp"); + RegisterNamespace(XMPConstConstants.NsCreatorAtom, "creatorAtom"); + RegisterNamespace(XMPConstConstants.NsAsf, "asf"); + RegisterNamespace(XMPConstConstants.NsWav, "wav"); + RegisterNamespace(XMPConstConstants.NsBwf, "bext"); + RegisterNamespace(XMPConstConstants.NsRiffinfo, "riffinfo"); + RegisterNamespace(XMPConstConstants.NsScript, "xmpScript"); + RegisterNamespace(XMPConstConstants.NsTxmp, "txmp"); + RegisterNamespace(XMPConstConstants.NsSwf, "swf"); + // register Adobe private namespaces + RegisterNamespace(XMPConstConstants.NsDm, "xmpDM"); + RegisterNamespace(XMPConstConstants.NsTransient, "xmpx"); + // register Adobe standard type namespaces + RegisterNamespace(XMPConstConstants.TypeText, "xmpT"); + RegisterNamespace(XMPConstConstants.TypePagedfile, "xmpTPg"); + RegisterNamespace(XMPConstConstants.TypeGraphics, "xmpG"); + RegisterNamespace(XMPConstConstants.TypeImage, "xmpGImg"); + RegisterNamespace(XMPConstConstants.TypeFont, "stFnt"); + RegisterNamespace(XMPConstConstants.TypeDimensions, "stDim"); + RegisterNamespace(XMPConstConstants.TypeResourceevent, "stEvt"); + RegisterNamespace(XMPConstConstants.TypeResourceref, "stRef"); + RegisterNamespace(XMPConstConstants.TypeStVersion, "stVer"); + RegisterNamespace(XMPConstConstants.TypeStJob, "stJob"); + RegisterNamespace(XMPConstConstants.TypeManifestitem, "stMfs"); + RegisterNamespace(XMPConstConstants.TypeIdentifierqual, "xmpidq"); + } + + // --------------------------------------------------------------------------------------------- + // Alias Functions + /// + public XMPAliasInfo ResolveAlias(string aliasNS, string aliasProp) + { + lock (this) + { + string aliasPrefix = GetNamespacePrefix(aliasNS); + if (aliasPrefix == null) + { + return null; + } + return (XMPAliasInfo)aliasMap.Get(aliasPrefix + aliasProp); + } + } + + /// + public XMPAliasInfo FindAlias(string qname) + { + lock (this) + { + return (XMPAliasInfo)aliasMap.Get(qname); + } + } + + /// + public XMPAliasInfo[] FindAliases(string aliasNS) + { + lock (this) + { + string prefix = GetNamespacePrefix(aliasNS); + IList result = new ArrayList(); + if (prefix != null) + { + for (Iterator it = aliasMap.Keys.Iterator(); it.HasNext(); ) + { + string qname = (string)it.Next(); + if (qname.StartsWith(prefix)) + { + result.Add(FindAlias(qname)); + } + } + } + return (XMPAliasInfo[])Collections.ToArray(result, new XMPAliasInfo[result.Count]); + } + } + + /// Associates an alias name with an actual name. + /// + /// Associates an alias name with an actual name. + ///

+ /// Define a alias mapping from one namespace/property to another. Both + /// property names must be simple names. An alias can be a direct mapping, + /// where the alias and actual have the same data type. It is also possible + /// to map a simple alias to an item in an array. This can either be to the + /// first item in the array, or to the 'x-default' item in an alt-text array. + /// Multiple alias names may map to the same actual, as long as the forms + /// match. It is a no-op to reregister an alias in an identical fashion. + /// Note: This method is not locking because only called by registerStandardAliases + /// which is only called by the constructor. + /// Note2: The method is only package-private so that it can be tested with unittests + /// + /// + /// The namespace URI for the alias. Must not be null or the empty + /// string. + /// + /// + /// The name of the alias. Must be a simple name, not null or the + /// empty string and not a general path expression. + /// + /// + /// The namespace URI for the actual. Must not be null or the + /// empty string. + /// + /// + /// The name of the actual. Must be a simple name, not null or the + /// empty string and not a general path expression. + /// + /// + /// Provides options for aliases for simple aliases to array + /// items. This is needed to know what kind of array to create if + /// set for the first time via the simple alias. Pass + /// XMP_NoOptions, the default value, for all + /// direct aliases regardless of whether the actual data type is + /// an array or not (see + /// + /// ). + /// + /// for inconsistant aliases. + internal void RegisterAlias(string aliasNS, string aliasProp, string actualNS, string actualProp, AliasOptions aliasForm) + { + lock (this) + { + ParameterAsserts.AssertSchemaNS(aliasNS); + ParameterAsserts.AssertPropName(aliasProp); + ParameterAsserts.AssertSchemaNS(actualNS); + ParameterAsserts.AssertPropName(actualProp); + // Fix the alias options + AliasOptions aliasOpts = aliasForm != null ? new AliasOptions(XMPNodeUtils.VerifySetOptions(aliasForm.ToPropertyOptions(), null).GetOptions()) : new AliasOptions(); + if (p.Matcher(aliasProp).Find() || p.Matcher(actualProp).Find()) + { + throw new XMPException("Alias and actual property names must be simple", XMPErrorConstants.Badxpath); + } + // check if both namespaces are registered + string aliasPrefix = GetNamespacePrefix(aliasNS); + string actualPrefix = GetNamespacePrefix(actualNS); + if (aliasPrefix == null) + { + throw new XMPException("Alias namespace is not registered", XMPErrorConstants.Badschema); + } + else + { + if (actualPrefix == null) + { + throw new XMPException("Actual namespace is not registered", XMPErrorConstants.Badschema); + } + } + string key = aliasPrefix + aliasProp; + // check if alias is already existing + if (aliasMap.ContainsKey(key)) + { + throw new XMPException("Alias is already existing", XMPErrorConstants.Badparam); + } + else + { + if (aliasMap.ContainsKey(actualPrefix + actualProp)) + { + throw new XMPException("Actual property is already an alias, use the base property", XMPErrorConstants.Badparam); + } + } + XMPAliasInfo aliasInfo = new _XMPAliasInfo_390(actualNS, actualPrefix, actualProp, aliasOpts); + aliasMap.Put(key, aliasInfo); + } + } + + private sealed class _XMPAliasInfo_390 : XMPAliasInfo + { + public _XMPAliasInfo_390(string actualNS, string actualPrefix, string actualProp, AliasOptions aliasOpts) + { + this.actualNS = actualNS; + this.actualPrefix = actualPrefix; + this.actualProp = actualProp; + this.aliasOpts = aliasOpts; + } + + /// + public string GetNamespace() + { + return actualNS; + } + + /// + public string GetPrefix() + { + return actualPrefix; + } + + /// + public string GetPropName() + { + return actualProp; + } + + /// + public AliasOptions GetAliasForm() + { + return aliasOpts; + } + + public override string ToString() + { + return actualPrefix + actualProp + " NS(" + actualNS + "), FORM (" + this.GetAliasForm() + ")"; + } + + private readonly string actualNS; + + private readonly string actualPrefix; + + private readonly string actualProp; + + private readonly AliasOptions aliasOpts; + } + + /// + public IDictionary GetAliases() + { + lock (this) + { + return Collections.UnmodifiableMap(new SortedList(aliasMap)); + } + } + + ///

Register the standard aliases. + /// + /// Register the standard aliases. + /// Note: This method is not lock because only called by the constructor. + /// + /// If the registrations of at least one alias fails. + private void RegisterStandardAliases() + { + AliasOptions aliasToArrayOrdered = new AliasOptions().SetArrayOrdered(true); + AliasOptions aliasToArrayAltText = new AliasOptions().SetArrayAltText(true); + // Aliases from XMP to DC. + RegisterAlias(XMPConstConstants.NsXmp, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); + RegisterAlias(XMPConstConstants.NsXmp, "Authors", XMPConstConstants.NsDc, "creator", null); + RegisterAlias(XMPConstConstants.NsXmp, "Description", XMPConstConstants.NsDc, "description", null); + RegisterAlias(XMPConstConstants.NsXmp, "Format", XMPConstConstants.NsDc, "format", null); + RegisterAlias(XMPConstConstants.NsXmp, "Keywords", XMPConstConstants.NsDc, "subject", null); + RegisterAlias(XMPConstConstants.NsXmp, "Locale", XMPConstConstants.NsDc, "language", null); + RegisterAlias(XMPConstConstants.NsXmp, "Title", XMPConstConstants.NsDc, "title", null); + RegisterAlias(XMPConstConstants.NsXmpRights, "Copyright", XMPConstConstants.NsDc, "rights", null); + // Aliases from PDF to DC and XMP. + RegisterAlias(XMPConstConstants.NsPdf, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); + RegisterAlias(XMPConstConstants.NsPdf, "BaseURL", XMPConstConstants.NsXmp, "BaseURL", null); + RegisterAlias(XMPConstConstants.NsPdf, "CreationDate", XMPConstConstants.NsXmp, "CreateDate", null); + RegisterAlias(XMPConstConstants.NsPdf, "Creator", XMPConstConstants.NsXmp, "CreatorTool", null); + RegisterAlias(XMPConstConstants.NsPdf, "ModDate", XMPConstConstants.NsXmp, "ModifyDate", null); + RegisterAlias(XMPConstConstants.NsPdf, "Subject", XMPConstConstants.NsDc, "description", aliasToArrayAltText); + RegisterAlias(XMPConstConstants.NsPdf, "Title", XMPConstConstants.NsDc, "title", aliasToArrayAltText); + // Aliases from PHOTOSHOP to DC and XMP. + RegisterAlias(XMPConstConstants.NsPhotoshop, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); + RegisterAlias(XMPConstConstants.NsPhotoshop, "Caption", XMPConstConstants.NsDc, "description", aliasToArrayAltText); + RegisterAlias(XMPConstConstants.NsPhotoshop, "Copyright", XMPConstConstants.NsDc, "rights", aliasToArrayAltText); + RegisterAlias(XMPConstConstants.NsPhotoshop, "Keywords", XMPConstConstants.NsDc, "subject", null); + RegisterAlias(XMPConstConstants.NsPhotoshop, "Marked", XMPConstConstants.NsXmpRights, "Marked", null); + RegisterAlias(XMPConstConstants.NsPhotoshop, "Title", XMPConstConstants.NsDc, "title", aliasToArrayAltText); + RegisterAlias(XMPConstConstants.NsPhotoshop, "WebStatement", XMPConstConstants.NsXmpRights, "WebStatement", null); + // Aliases from TIFF and EXIF to DC and XMP. + RegisterAlias(XMPConstConstants.NsTiff, "Artist", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); + RegisterAlias(XMPConstConstants.NsTiff, "Copyright", XMPConstConstants.NsDc, "rights", null); + RegisterAlias(XMPConstConstants.NsTiff, "DateTime", XMPConstConstants.NsXmp, "ModifyDate", null); + RegisterAlias(XMPConstConstants.NsTiff, "ImageDescription", XMPConstConstants.NsDc, "description", null); + RegisterAlias(XMPConstConstants.NsTiff, "Software", XMPConstConstants.NsXmp, "CreatorTool", null); + // Aliases from PNG (Acrobat ImageCapture) to DC and XMP. + RegisterAlias(XMPConstConstants.NsPng, "Author", XMPConstConstants.NsDc, "creator", aliasToArrayOrdered); + RegisterAlias(XMPConstConstants.NsPng, "Copyright", XMPConstConstants.NsDc, "rights", aliasToArrayAltText); + RegisterAlias(XMPConstConstants.NsPng, "CreationTime", XMPConstConstants.NsXmp, "CreateDate", null); + RegisterAlias(XMPConstConstants.NsPng, "Description", XMPConstConstants.NsDc, "description", aliasToArrayAltText); + RegisterAlias(XMPConstConstants.NsPng, "ModificationTime", XMPConstConstants.NsXmp, "ModifyDate", null); + RegisterAlias(XMPConstConstants.NsPng, "Software", XMPConstConstants.NsXmp, "CreatorTool", null); + RegisterAlias(XMPConstConstants.NsPng, "Title", XMPConstConstants.NsDc, "title", aliasToArrayAltText); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerHelper.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerHelper.cs index fabec0547..cabbbaf15 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerHelper.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerHelper.cs @@ -1,92 +1,92 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using Com.Adobe.Xmp.Options; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// - /// Serializes the XMPMeta-object to an OutputStream according to the - /// SerializeOptions. - /// - /// 11.07.2006 - public class XMPSerializerHelper - { - /// Static method to serialize the metadata object. - /// - /// Static method to serialize the metadata object. For each serialisation, a new XMPSerializer - /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so thats its possible to - /// serialialize the same XMPMeta objects in two threads. - /// - /// a metadata implementation object - /// the output stream to serialize to - /// serialization options, can be null for default. - /// - public static void Serialize(XMPMetaImpl xmp, OutputStream @out, SerializeOptions options) - { - options = options != null ? options : new SerializeOptions(); - // sort the internal data model on demand - if (options.GetSort()) - { - xmp.Sort(); - } - new XMPSerializerRDF().Serialize(xmp, @out, options); - } - - /// Serializes an XMPMeta-object as RDF into a string. - /// - /// Serializes an XMPMeta-object as RDF into a string. - /// Note: Encoding is forced to UTF-16 when serializing to a - /// string to ensure the correctness of "exact packet size". - /// - /// a metadata implementation object - /// - /// Options to control the serialization (see - /// - /// ). - /// - /// Returns a string containing the serialized RDF. - /// on serializsation errors. - public static string SerializeToString(XMPMetaImpl xmp, SerializeOptions options) - { - // forces the encoding to be UTF-16 to get the correct string length - options = options != null ? options : new SerializeOptions(); - options.SetEncodeUTF16BE(true); - ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); - Serialize(xmp, @out, options); - try - { - return @out.ToString(options.GetEncoding()); - } - catch (UnsupportedEncodingException) - { - // cannot happen as UTF-8/16LE/BE is required to be implemented in - // Java - return @out.ToString(); - } - } - - /// Serializes an XMPMeta-object as RDF into a byte buffer. - /// a metadata implementation object - /// - /// Options to control the serialization (see - /// - /// ). - /// - /// Returns a byte buffer containing the serialized RDF. - /// on serializsation errors. - public static sbyte[] SerializeToBuffer(XMPMetaImpl xmp, SerializeOptions options) - { - ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); - Serialize(xmp, @out, options); - return @out.ToByteArray(); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using Com.Adobe.Xmp.Options; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// + /// Serializes the XMPMeta-object to an OutputStream according to the + /// SerializeOptions. + /// + /// 11.07.2006 + public class XMPSerializerHelper + { + /// Static method to serialize the metadata object. + /// + /// Static method to serialize the metadata object. For each serialisation, a new XMPSerializer + /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so thats its possible to + /// serialialize the same XMPMeta objects in two threads. + /// + /// a metadata implementation object + /// the output stream to serialize to + /// serialization options, can be null for default. + /// + public static void Serialize(XMPMetaImpl xmp, OutputStream @out, SerializeOptions options) + { + options = options != null ? options : new SerializeOptions(); + // sort the internal data model on demand + if (options.GetSort()) + { + xmp.Sort(); + } + new XMPSerializerRDF().Serialize(xmp, @out, options); + } + + /// Serializes an XMPMeta-object as RDF into a string. + /// + /// Serializes an XMPMeta-object as RDF into a string. + /// Note: Encoding is forced to UTF-16 when serializing to a + /// string to ensure the correctness of "exact packet size". + /// + /// a metadata implementation object + /// + /// Options to control the serialization (see + /// + /// ). + /// + /// Returns a string containing the serialized RDF. + /// on serializsation errors. + public static string SerializeToString(XMPMetaImpl xmp, SerializeOptions options) + { + // forces the encoding to be UTF-16 to get the correct string length + options = options != null ? options : new SerializeOptions(); + options.SetEncodeUTF16BE(true); + ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); + Serialize(xmp, @out, options); + try + { + return @out.ToString(options.GetEncoding()); + } + catch (UnsupportedEncodingException) + { + // cannot happen as UTF-8/16LE/BE is required to be implemented in + // Java + return @out.ToString(); + } + } + + /// Serializes an XMPMeta-object as RDF into a byte buffer. + /// a metadata implementation object + /// + /// Options to control the serialization (see + /// + /// ). + /// + /// Returns a byte buffer containing the serialized RDF. + /// on serializsation errors. + public static sbyte[] SerializeToBuffer(XMPMetaImpl xmp, SerializeOptions options) + { + ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); + Serialize(xmp, @out, options); + return @out.ToByteArray(); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerRDF.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerRDF.cs index f349f22fd..63fcdbc07 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerRDF.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPSerializerRDF.cs @@ -1,1228 +1,1228 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Collections.Generic; -using System.IO; -using Com.Adobe.Xmp.Options; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// Serializes the XMPMeta-object using the standard RDF serialization format. - /// - /// Serializes the XMPMeta-object using the standard RDF serialization format. - /// The output is written to an OutputStream - /// according to the SerializeOptions. - /// - /// 11.07.2006 - public class XMPSerializerRDF - { - /// default padding - private const int DefaultPad = 2048; - - private const string PacketHeader = ""; - - /// The w/r is missing inbetween - private const string PacketTrailer = ""; - - private const string RdfXmpmetaStart = ""; - - private const string RdfRdfEnd = ""; - - private const string RdfSchemaStart = ""; - - private const string RdfStructStart = "a set of all rdf attribute qualifier - internal static readonly ICollection RdfAttrQualifier = new HashSet(Arrays.AsList(new string[] { XMPConstConstants.XmlLang, "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID" })); - - /// the metadata object to be serialized. - private XMPMetaImpl xmp; - - /// the output stream to serialize to - private CountOutputStream outputStream; - - /// this writer is used to do the actual serialization - private OutputStreamWriter writer; - - /// the stored serialization options - private SerializeOptions options; - - /// - /// the size of one unicode char, for UTF-8 set to 1 - /// (Note: only valid for ASCII chars lower than 0x80), - /// set to 2 in case of UTF-16 - /// - private int unicodeSize = 1; - - /// - /// the padding in the XMP Packet, or the length of the complete packet in - /// case of option exactPacketLength. - /// - private int padding; - - // UTF-8 - /// The actual serialization. - /// the metadata object to be serialized - /// outputStream the output stream to serialize to - /// the serialization options - /// If case of wrong options or any other serialization error. - public virtual void Serialize(XMPMeta xmp, OutputStream @out, SerializeOptions options) - { - try - { - outputStream = new CountOutputStream(@out); - writer = new OutputStreamWriter(outputStream, options.GetEncoding()); - this.xmp = (XMPMetaImpl)xmp; - this.options = options; - this.padding = options.GetPadding(); - writer = new OutputStreamWriter(outputStream, options.GetEncoding()); - CheckOptionsConsistence(); - // serializes the whole packet, but don't write the tail yet - // and flush to make sure that the written bytes are calculated correctly - string tailStr = SerializeAsRDF(); - writer.Flush(); - // adds padding - AddPadding(tailStr.Length); - // writes the tail - Write(tailStr); - writer.Flush(); - outputStream.Close(); - } - catch (IOException) - { - throw new XMPException("Error writing to the OutputStream", XMPErrorConstants.Unknown); - } - } - - /// Calculates the padding according to the options and write it to the stream. - /// the length of the tail string - /// thrown if packet size is to small to fit the padding - /// forwards writer errors - private void AddPadding(int tailLength) - { - if (options.GetExactPacketLength()) - { - // the string length is equal to the length of the UTF-8 encoding - int minSize = outputStream.GetBytesWritten() + tailLength * unicodeSize; - if (minSize > padding) - { - throw new XMPException("Can't fit into specified packet size", XMPErrorConstants.Badserialize); - } - padding -= minSize; - } - // Now the actual amount of padding to add. - // fix rest of the padding according to Unicode unit size. - padding /= unicodeSize; - int newlineLen = options.GetNewline().Length; - if (padding >= newlineLen) - { - padding -= newlineLen; - // Write this newline last. - while (padding >= (100 + newlineLen)) - { - WriteChars(100, ' '); - WriteNewline(); - padding -= (100 + newlineLen); - } - WriteChars(padding, ' '); - WriteNewline(); - } - else - { - WriteChars(padding, ' '); - } - } - - /// Checks if the supplied options are consistent. - /// Thrown if options are conflicting - protected internal virtual void CheckOptionsConsistence() - { - if (options.GetEncodeUTF16BE() | options.GetEncodeUTF16LE()) - { - unicodeSize = 2; - } - if (options.GetExactPacketLength()) - { - if (options.GetOmitPacketWrapper() | options.GetIncludeThumbnailPad()) - { - throw new XMPException("Inconsistent options for exact size serialize", XMPErrorConstants.Badoptions); - } - if ((options.GetPadding() & (unicodeSize - 1)) != 0) - { - throw new XMPException("Exact size must be a multiple of the Unicode element", XMPErrorConstants.Badoptions); - } - } - else - { - if (options.GetReadOnlyPacket()) - { - if (options.GetOmitPacketWrapper() | options.GetIncludeThumbnailPad()) - { - throw new XMPException("Inconsistent options for read-only packet", XMPErrorConstants.Badoptions); - } - padding = 0; - } - else - { - if (options.GetOmitPacketWrapper()) - { - if (options.GetIncludeThumbnailPad()) - { - throw new XMPException("Inconsistent options for non-packet serialize", XMPErrorConstants.Badoptions); - } - padding = 0; - } - else - { - if (padding == 0) - { - padding = DefaultPad * unicodeSize; - } - if (options.GetIncludeThumbnailPad()) - { - if (!xmp.DoesPropertyExist(XMPConstConstants.NsXmp, "Thumbnails")) - { - padding += 10000 * unicodeSize; - } - } - } - } - } - } - - /// Writes the (optional) packet header and the outer rdf-tags. - /// Returns the packet end processing instraction to be written after the padding. - /// Forwarded writer exceptions. - /// - private string SerializeAsRDF() - { - int level = 0; - // Write the packet header PI. - if (!options.GetOmitPacketWrapper()) - { - WriteIndent(level); - Write(PacketHeader); - WriteNewline(); - } - // Write the x:xmpmeta element's start tag. - if (!options.GetOmitXmpMetaElement()) - { - WriteIndent(level); - Write(RdfXmpmetaStart); - // Note: this flag can only be set by unit tests - if (!options.GetOmitVersionAttribute()) - { - Write(XMPMetaFactory.GetVersionInfo().GetMessage()); - } - Write("\">"); - WriteNewline(); - level++; - } - // Write the rdf:RDF start tag. - WriteIndent(level); - Write(RdfRdfStart); - WriteNewline(); - // Write all of the properties. - if (options.GetUseCanonicalFormat()) - { - SerializeCanonicalRDFSchemas(level); - } - else - { - SerializeCompactRDFSchemas(level); - } - // Write the rdf:RDF end tag. - WriteIndent(level); - Write(RdfRdfEnd); - WriteNewline(); - // Write the xmpmeta end tag. - if (!options.GetOmitXmpMetaElement()) - { - level--; - WriteIndent(level); - Write(RdfXmpmetaEnd); - WriteNewline(); - } - // Write the packet trailer PI into the tail string as UTF-8. - string tailStr = string.Empty; - if (!options.GetOmitPacketWrapper()) - { - for (level = options.GetBaseIndent(); level > 0; level--) - { - tailStr += options.GetIndent(); - } - tailStr += PacketTrailer; - tailStr += options.GetReadOnlyPacket() ? 'r' : 'w'; - tailStr += PacketTrailer2; - } - return tailStr; - } - - /// Serializes the metadata in pretty-printed manner. - /// indent level - /// Forwarded writer exceptions - /// - private void SerializeCanonicalRDFSchemas(int level) - { - if (xmp.GetRoot().GetChildrenLength() > 0) - { - StartOuterRDFDescription(xmp.GetRoot(), level); - for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); ) - { - XMPNode currSchema = (XMPNode)it.Next(); - SerializeCanonicalRDFSchema(currSchema, level); - } - EndOuterRDFDescription(level); - } - else - { - WriteIndent(level + 1); - Write(RdfSchemaStart); - // Special case an empty XMP object. - WriteTreeName(); - Write("/>"); - WriteNewline(); - } - } - - /// - private void WriteTreeName() - { - Write('"'); - string name = xmp.GetRoot().GetName(); - if (name != null) - { - AppendNodeValue(name, true); - } - Write('"'); - } - - /// Serializes the metadata in compact manner. - /// indent level to start with - /// Forwarded writer exceptions - /// - private void SerializeCompactRDFSchemas(int level) - { - // Begin the rdf:Description start tag. - WriteIndent(level + 1); - Write(RdfSchemaStart); - WriteTreeName(); - // Write all necessary xmlns attributes. - ICollection usedPrefixes = new HashSet(); - usedPrefixes.Add("xml"); - usedPrefixes.Add("rdf"); - for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); ) - { - XMPNode schema = (XMPNode)it.Next(); - DeclareUsedNamespaces(schema, usedPrefixes, level + 3); - } - // Write the top level "attrProps" and close the rdf:Description start tag. - bool allAreAttrs = true; - for (Iterator it_1 = xmp.GetRoot().IterateChildren(); it_1.HasNext(); ) - { - XMPNode schema = (XMPNode)it_1.Next(); - allAreAttrs &= SerializeCompactRDFAttrProps(schema, level + 2); - } - if (!allAreAttrs) - { - Write('>'); - WriteNewline(); - } - else - { - Write("/>"); - WriteNewline(); - return; - } - // ! Done if all properties in all schema are written as attributes. - // Write the remaining properties for each schema. - for (Iterator it_2 = xmp.GetRoot().IterateChildren(); it_2.HasNext(); ) - { - XMPNode schema = (XMPNode)it_2.Next(); - SerializeCompactRDFElementProps(schema, level + 2); - } - // Write the rdf:Description end tag. - WriteIndent(level + 1); - Write(RdfSchemaEnd); - WriteNewline(); - } - - /// Write each of the parent's simple unqualified properties as an attribute. - /// - /// Write each of the parent's simple unqualified properties as an attribute. Returns true if all - /// of the properties are written as attributes. - /// - /// the parent property node - /// the current indent level - /// Returns true if all properties can be rendered as RDF attribute. - /// - private bool SerializeCompactRDFAttrProps(XMPNode parentNode, int indent) - { - bool allAreAttrs = true; - for (Iterator it = parentNode.IterateChildren(); it.HasNext(); ) - { - XMPNode prop = (XMPNode)it.Next(); - if (CanBeRDFAttrProp(prop)) - { - WriteNewline(); - WriteIndent(indent); - Write(prop.GetName()); - Write("=\""); - AppendNodeValue(prop.GetValue(), true); - Write('"'); - } - else - { - allAreAttrs = false; - } - } - return allAreAttrs; - } - - /// - /// Recursively handles the "value" for a node that must be written as an RDF - /// property element. - /// - /// - /// Recursively handles the "value" for a node that must be written as an RDF - /// property element. It does not matter if it is a top level property, a - /// field of a struct, or an item of an array. The indent is that for the - /// property element. The patterns bwlow ignore attribute qualifiers such as - /// xml:lang, they don't affect the output form. - ///
- ///
-        /// <ns:UnqualifiedStructProperty-1
-        /// ... The fields as attributes, if all are simple and unqualified
-        /// />
-        /// <ns:UnqualifiedStructProperty-2 rdf:parseType="Resource">
-        /// ... The fields as elements, if none are simple and unqualified
-        /// </ns:UnqualifiedStructProperty-2>
-        /// <ns:UnqualifiedStructProperty-3>
-        /// <rdf:Description
-        /// ... The simple and unqualified fields as attributes
-        /// >
-        /// ... The compound or qualified fields as elements
-        /// </rdf:Description>
-        /// </ns:UnqualifiedStructProperty-3>
-        /// <ns:UnqualifiedArrayProperty>
-        /// <rdf:Bag> or Seq or Alt
-        /// ... Array items as rdf:li elements, same forms as top level properties
-        /// </rdf:Bag>
-        /// </ns:UnqualifiedArrayProperty>
-        /// <ns:QualifiedProperty rdf:parseType="Resource">
-        /// <rdf:value> ... Property "value"
-        /// following the unqualified forms ... </rdf:value>
-        /// ... Qualifiers looking like named struct fields
-        /// </ns:QualifiedProperty>
-        /// 
- ///
- /// *** Consider numbered array items, but has compatibility problems. - /// Consider qualified form with rdf:Description and attributes. - ///
- /// the parent node - /// the current indent level - /// Forwards writer exceptions - /// If qualifier and element fields are mixed. - private void SerializeCompactRDFElementProps(XMPNode parentNode, int indent) - { - for (Iterator it = parentNode.IterateChildren(); it.HasNext(); ) - { - XMPNode node = (XMPNode)it.Next(); - if (CanBeRDFAttrProp(node)) - { - continue; - } - bool emitEndTag = true; - bool indentEndTag = true; - // Determine the XML element name, write the name part of the start tag. Look over the - // qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute - // qualifiers at the same time. - string elemName = node.GetName(); - if (XMPConstConstants.ArrayItemName.Equals(elemName)) - { - elemName = "rdf:li"; - } - WriteIndent(indent); - Write('<'); - Write(elemName); - bool hasGeneralQualifiers = false; - bool hasRDFResourceQual = false; - for (Iterator iq = node.IterateQualifier(); iq.HasNext(); ) - { - XMPNode qualifier = (XMPNode)iq.Next(); - if (!RdfAttrQualifier.Contains(qualifier.GetName())) - { - hasGeneralQualifiers = true; - } - else - { - hasRDFResourceQual = "rdf:resource".Equals(qualifier.GetName()); - Write(' '); - Write(qualifier.GetName()); - Write("=\""); - AppendNodeValue(qualifier.GetValue(), true); - Write('"'); - } - } - // Process the property according to the standard patterns. - if (hasGeneralQualifiers) - { - SerializeCompactRDFGeneralQualifier(indent, node); - } - else - { - // This node has only attribute qualifiers. Emit as a property element. - if (!node.GetOptions().IsCompositeProperty()) - { - object[] result = SerializeCompactRDFSimpleProp(node); - emitEndTag = ((bool)result[0]); - indentEndTag = ((bool)result[1]); - } - else - { - if (node.GetOptions().IsArray()) - { - SerializeCompactRDFArrayProp(node, indent); - } - else - { - emitEndTag = SerializeCompactRDFStructProp(node, indent, hasRDFResourceQual); - } - } - } - // Emit the property element end tag. - if (emitEndTag) - { - if (indentEndTag) - { - WriteIndent(indent); - } - Write("'); - WriteNewline(); - } - } - } - - /// Serializes a simple property. - /// an XMPNode - /// Returns an array containing the flags emitEndTag and indentEndTag. - /// Forwards the writer exceptions. - private object[] SerializeCompactRDFSimpleProp(XMPNode node) - { - // This is a simple property. - bool emitEndTag = true; - bool indentEndTag = true; - if (node.GetOptions().IsURI()) - { - Write(" rdf:resource=\""); - AppendNodeValue(node.GetValue(), true); - Write("\"/>"); - WriteNewline(); - emitEndTag = false; - } - else - { - if (node.GetValue() == null || node.GetValue().Length == 0) - { - Write("/>"); - WriteNewline(); - emitEndTag = false; - } - else - { - Write('>'); - AppendNodeValue(node.GetValue(), false); - indentEndTag = false; - } - } - return new object[] { emitEndTag, indentEndTag }; - } - - /// Serializes an array property. - /// an XMPNode - /// the current indent level - /// Forwards the writer exceptions. - /// If qualifier and element fields are mixed. - private void SerializeCompactRDFArrayProp(XMPNode node, int indent) - { - // This is an array. - Write('>'); - WriteNewline(); - EmitRDFArrayTag(node, true, indent + 1); - if (node.GetOptions().IsArrayAltText()) - { - XMPNodeUtils.NormalizeLangArray(node); - } - SerializeCompactRDFElementProps(node, indent + 2); - EmitRDFArrayTag(node, false, indent + 1); - } - - /// Serializes a struct property. - /// an XMPNode - /// the current indent level - /// Flag if the element has resource qualifier - /// Returns true if an end flag shall be emitted. - /// Forwards the writer exceptions. - /// If qualifier and element fields are mixed. - private bool SerializeCompactRDFStructProp(XMPNode node, int indent, bool hasRDFResourceQual) - { - // This must be a struct. - bool hasAttrFields = false; - bool hasElemFields = false; - bool emitEndTag = true; - for (Iterator ic = node.IterateChildren(); ic.HasNext(); ) - { - XMPNode field = (XMPNode)ic.Next(); - if (CanBeRDFAttrProp(field)) - { - hasAttrFields = true; - } - else - { - hasElemFields = true; - } - if (hasAttrFields && hasElemFields) - { - break; - } - } - // No sense looking further. - if (hasRDFResourceQual && hasElemFields) - { - throw new XMPException("Can't mix rdf:resource qualifier and element fields", XMPErrorConstants.Badrdf); - } - if (!node.HasChildren()) - { - // Catch an empty struct as a special case. The case - // below would emit an empty - // XML element, which gets reparsed as a simple property - // with an empty value. - Write(" rdf:parseType=\"Resource\"/>"); - WriteNewline(); - emitEndTag = false; - } - else - { - if (!hasElemFields) - { - // All fields can be attributes, use the - // emptyPropertyElt form. - SerializeCompactRDFAttrProps(node, indent + 1); - Write("/>"); - WriteNewline(); - emitEndTag = false; - } - else - { - if (!hasAttrFields) - { - // All fields must be elements, use the - // parseTypeResourcePropertyElt form. - Write(" rdf:parseType=\"Resource\">"); - WriteNewline(); - SerializeCompactRDFElementProps(node, indent + 1); - } - else - { - // Have a mix of attributes and elements, use an inner rdf:Description. - Write('>'); - WriteNewline(); - WriteIndent(indent + 1); - Write(RdfStructStart); - SerializeCompactRDFAttrProps(node, indent + 2); - Write(">"); - WriteNewline(); - SerializeCompactRDFElementProps(node, indent + 1); - WriteIndent(indent + 1); - Write(RdfStructEnd); - WriteNewline(); - } - } - } - return emitEndTag; - } - - /// Serializes the general qualifier. - /// the root node of the subtree - /// the current indent level - /// Forwards all writer exceptions. - /// If qualifier and element fields are mixed. - private void SerializeCompactRDFGeneralQualifier(int indent, XMPNode node) - { - // The node has general qualifiers, ones that can't be - // attributes on a property element. - // Emit using the qualified property pseudo-struct form. The - // value is output by a call - // to SerializePrettyRDFProperty with emitAsRDFValue set. - Write(" rdf:parseType=\"Resource\">"); - WriteNewline(); - SerializeCanonicalRDFProperty(node, false, true, indent + 1); - for (Iterator iq = node.IterateQualifier(); iq.HasNext(); ) - { - XMPNode qualifier = (XMPNode)iq.Next(); - SerializeCanonicalRDFProperty(qualifier, false, false, indent + 1); - } - } - - /// - /// Serializes one schema with all contained properties in pretty-printed - /// manner.
- /// Each schema's properties are written to a single - /// rdf:Description element. - ///
- /// - /// Serializes one schema with all contained properties in pretty-printed - /// manner.
- /// Each schema's properties are written to a single - /// rdf:Description element. All of the necessary namespaces are declared in - /// the rdf:Description element. The baseIndent is the base level for the - /// entire serialization, that of the x:xmpmeta element. An xml:lang - /// qualifier is written as an attribute of the property start tag, not by - /// itself forcing the qualified property form. - ///
- ///
-        /// <rdf:Description rdf:about="TreeName" xmlns:ns="URI" ... >
-        /// ... The actual properties of the schema, see SerializePrettyRDFProperty
-        /// <!-- ns1:Alias is aliased to ns2:Actual -->  ... If alias comments are wanted
-        /// </rdf:Description>
-        /// 
- ///
- ///
- /// a schema node - /// - /// Forwarded writer exceptions - /// - private void SerializeCanonicalRDFSchema(XMPNode schemaNode, int level) - { - // Write each of the schema's actual properties. - for (Iterator it = schemaNode.IterateChildren(); it.HasNext(); ) - { - XMPNode propNode = (XMPNode)it.Next(); - SerializeCanonicalRDFProperty(propNode, options.GetUseCanonicalFormat(), false, level + 2); - } - } - - /// Writes all used namespaces of the subtree in node to the output. - /// - /// Writes all used namespaces of the subtree in node to the output. - /// The subtree is recursivly traversed. - /// - /// the root node of the subtree - /// a set containing currently used prefixes - /// the current indent level - /// Forwards all writer exceptions. - private void DeclareUsedNamespaces(XMPNode node, ICollection usedPrefixes, int indent) - { - if (node.GetOptions().IsSchemaNode()) - { - // The schema node name is the URI, the value is the prefix. - string prefix = Runtime.Substring(node.GetValue(), 0, node.GetValue().Length - 1); - DeclareNamespace(prefix, node.GetName(), usedPrefixes, indent); - } - else - { - if (node.GetOptions().IsStruct()) - { - for (Iterator it = node.IterateChildren(); it.HasNext(); ) - { - XMPNode field = (XMPNode)it.Next(); - DeclareNamespace(field.GetName(), null, usedPrefixes, indent); - } - } - } - for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) - { - XMPNode child = (XMPNode)it_1.Next(); - DeclareUsedNamespaces(child, usedPrefixes, indent); - } - for (Iterator it_2 = node.IterateQualifier(); it_2.HasNext(); ) - { - XMPNode qualifier = (XMPNode)it_2.Next(); - DeclareNamespace(qualifier.GetName(), null, usedPrefixes, indent); - DeclareUsedNamespaces(qualifier, usedPrefixes, indent); - } - } - - /// Writes one namespace declaration to the output. - /// a namespace prefix (without colon) or a complete qname (when namespace == null) - /// the a namespace - /// a set containing currently used prefixes - /// the current indent level - /// Forwards all writer exceptions. - private void DeclareNamespace(string prefix, string @namespace, ICollection usedPrefixes, int indent) - { - if (@namespace == null) - { - // prefix contains qname, extract prefix and lookup namespace with prefix - QName qname = new QName(prefix); - if (qname.HasPrefix()) - { - prefix = qname.GetPrefix(); - // add colon for lookup - @namespace = XMPMetaFactory.GetSchemaRegistry().GetNamespaceURI(prefix + ":"); - // prefix w/o colon - DeclareNamespace(prefix, @namespace, usedPrefixes, indent); - } - else - { - return; - } - } - if (!usedPrefixes.Contains(prefix)) - { - WriteNewline(); - WriteIndent(indent); - Write("xmlns:"); - Write(prefix); - Write("=\""); - Write(@namespace); - Write('"'); - usedPrefixes.Add(prefix); - } - } - - /// Start the outer rdf:Description element, including all needed xmlns attributes. - /// - /// Start the outer rdf:Description element, including all needed xmlns attributes. - /// Leave the element open so that the compact form can add property attributes. - /// - /// If the writing to - private void StartOuterRDFDescription(XMPNode schemaNode, int level) - { - WriteIndent(level + 1); - Write(RdfSchemaStart); - WriteTreeName(); - ICollection usedPrefixes = new HashSet(); - usedPrefixes.Add("xml"); - usedPrefixes.Add("rdf"); - DeclareUsedNamespaces(schemaNode, usedPrefixes, level + 3); - Write('>'); - WriteNewline(); - } - - /// Write the end tag. - /// - private void EndOuterRDFDescription(int level) - { - WriteIndent(level + 1); - Write(RdfSchemaEnd); - WriteNewline(); - } - - /// Recursively handles the "value" for a node. - /// - /// Recursively handles the "value" for a node. It does not matter if it is a - /// top level property, a field of a struct, or an item of an array. The - /// indent is that for the property element. An xml:lang qualifier is written - /// as an attribute of the property start tag, not by itself forcing the - /// qualified property form. The patterns below mostly ignore attribute - /// qualifiers like xml:lang. Except for the one struct case, attribute - /// qualifiers don't affect the output form. - ///
- ///
-        /// <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty>
-        /// <ns:UnqualifiedStructProperty> (If no rdf:resource qualifier)
-        /// <rdf:Description>
-        /// ... Fields, same forms as top level properties
-        /// </rdf:Description>
-        /// </ns:UnqualifiedStructProperty>
-        /// <ns:ResourceStructProperty rdf:resource="URI"
-        /// ... Fields as attributes
-        /// >
-        /// <ns:UnqualifiedArrayProperty>
-        /// <rdf:Bag> or Seq or Alt
-        /// ... Array items as rdf:li elements, same forms as top level properties
-        /// </rdf:Bag>
-        /// </ns:UnqualifiedArrayProperty>
-        /// <ns:QualifiedProperty>
-        /// <rdf:Description>
-        /// <rdf:value> ... Property "value" following the unqualified
-        /// forms ... </rdf:value>
-        /// ... Qualifiers looking like named struct fields
-        /// </rdf:Description>
-        /// </ns:QualifiedProperty>
-        /// 
- ///
- ///
- /// the property node - /// property shall be rendered as attribute rather than tag - /// - /// use canonical form with inner description tag or - /// the compact form with rdf:ParseType="resource" attribute. - /// - /// the current indent level - /// Forwards all writer exceptions. - /// If "rdf:resource" and general qualifiers are mixed. - private void SerializeCanonicalRDFProperty(XMPNode node, bool useCanonicalRDF, bool emitAsRDFValue, int indent) - { - bool emitEndTag = true; - bool indentEndTag = true; - // Determine the XML element name. Open the start tag with the name and - // attribute qualifiers. - string elemName = node.GetName(); - if (emitAsRDFValue) - { - elemName = "rdf:value"; - } - else - { - if (XMPConstConstants.ArrayItemName.Equals(elemName)) - { - elemName = "rdf:li"; - } - } - WriteIndent(indent); - Write('<'); - Write(elemName); - bool hasGeneralQualifiers = false; - bool hasRDFResourceQual = false; - for (Iterator it = node.IterateQualifier(); it.HasNext(); ) - { - XMPNode qualifier = (XMPNode)it.Next(); - if (!RdfAttrQualifier.Contains(qualifier.GetName())) - { - hasGeneralQualifiers = true; - } - else - { - hasRDFResourceQual = "rdf:resource".Equals(qualifier.GetName()); - if (!emitAsRDFValue) - { - Write(' '); - Write(qualifier.GetName()); - Write("=\""); - AppendNodeValue(qualifier.GetValue(), true); - Write('"'); - } - } - } - // Process the property according to the standard patterns. - if (hasGeneralQualifiers && !emitAsRDFValue) - { - // This node has general, non-attribute, qualifiers. Emit using the - // qualified property form. - // ! The value is output by a recursive call ON THE SAME NODE with - // emitAsRDFValue set. - if (hasRDFResourceQual) - { - throw new XMPException("Can't mix rdf:resource and general qualifiers", XMPErrorConstants.Badrdf); - } - // Change serialization to canonical format with inner rdf:Description-tag - // depending on option - if (useCanonicalRDF) - { - Write(">"); - WriteNewline(); - indent++; - WriteIndent(indent); - Write(RdfStructStart); - Write(">"); - } - else - { - Write(" rdf:parseType=\"Resource\">"); - } - WriteNewline(); - SerializeCanonicalRDFProperty(node, useCanonicalRDF, true, indent + 1); - for (Iterator it_1 = node.IterateQualifier(); it_1.HasNext(); ) - { - XMPNode qualifier = (XMPNode)it_1.Next(); - if (!RdfAttrQualifier.Contains(qualifier.GetName())) - { - SerializeCanonicalRDFProperty(qualifier, useCanonicalRDF, false, indent + 1); - } - } - if (useCanonicalRDF) - { - WriteIndent(indent); - Write(RdfStructEnd); - WriteNewline(); - indent--; - } - } - else - { - // This node has no general qualifiers. Emit using an unqualified form. - if (!node.GetOptions().IsCompositeProperty()) - { - // This is a simple property. - if (node.GetOptions().IsURI()) - { - Write(" rdf:resource=\""); - AppendNodeValue(node.GetValue(), true); - Write("\"/>"); - WriteNewline(); - emitEndTag = false; - } - else - { - if (node.GetValue() == null || string.Empty.Equals(node.GetValue())) - { - Write("/>"); - WriteNewline(); - emitEndTag = false; - } - else - { - Write('>'); - AppendNodeValue(node.GetValue(), false); - indentEndTag = false; - } - } - } - else - { - if (node.GetOptions().IsArray()) - { - // This is an array. - Write('>'); - WriteNewline(); - EmitRDFArrayTag(node, true, indent + 1); - if (node.GetOptions().IsArrayAltText()) - { - XMPNodeUtils.NormalizeLangArray(node); - } - for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) - { - XMPNode child = (XMPNode)it_1.Next(); - SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 2); - } - EmitRDFArrayTag(node, false, indent + 1); - } - else - { - if (!hasRDFResourceQual) - { - // This is a "normal" struct, use the rdf:parseType="Resource" form. - if (!node.HasChildren()) - { - // Change serialization to canonical format with inner rdf:Description-tag - // if option is set - if (useCanonicalRDF) - { - Write(">"); - WriteNewline(); - WriteIndent(indent + 1); - Write(RdfEmptyStruct); - } - else - { - Write(" rdf:parseType=\"Resource\"/>"); - emitEndTag = false; - } - WriteNewline(); - } - else - { - // Change serialization to canonical format with inner rdf:Description-tag - // if option is set - if (useCanonicalRDF) - { - Write(">"); - WriteNewline(); - indent++; - WriteIndent(indent); - Write(RdfStructStart); - Write(">"); - } - else - { - Write(" rdf:parseType=\"Resource\">"); - } - WriteNewline(); - for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) - { - XMPNode child = (XMPNode)it_1.Next(); - SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 1); - } - if (useCanonicalRDF) - { - WriteIndent(indent); - Write(RdfStructEnd); - WriteNewline(); - indent--; - } - } - } - else - { - // This is a struct with an rdf:resource attribute, use the - // "empty property element" form. - for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) - { - XMPNode child = (XMPNode)it_1.Next(); - if (!CanBeRDFAttrProp(child)) - { - throw new XMPException("Can't mix rdf:resource and complex fields", XMPErrorConstants.Badrdf); - } - WriteNewline(); - WriteIndent(indent + 1); - Write(' '); - Write(child.GetName()); - Write("=\""); - AppendNodeValue(child.GetValue(), true); - Write('"'); - } - Write("/>"); - WriteNewline(); - emitEndTag = false; - } - } - } - } - // Emit the property element end tag. - if (emitEndTag) - { - if (indentEndTag) - { - WriteIndent(indent); - } - Write("'); - WriteNewline(); - } - } - - /// Writes the array start and end tags. - /// an array node - /// flag if its the start or end tag - /// the current indent level - /// forwards writer exceptions - private void EmitRDFArrayTag(XMPNode arrayNode, bool isStartTag, int indent) - { - if (isStartTag || arrayNode.HasChildren()) - { - WriteIndent(indent); - Write(isStartTag ? ""); - } - else - { - Write(">"); - } - WriteNewline(); - } - } - - /// Serializes the node value in XML encoding. - /// - /// Serializes the node value in XML encoding. Its used for tag bodies and - /// attributes. Note: The attribute is always limited by quotes, - /// thats why &apos; is never serialized. Note: - /// Control chars are written unescaped, but if the user uses others than tab, LF - /// and CR the resulting XML will become invalid. - /// - /// the value of the node - /// flag if value is an attribute value - /// - private void AppendNodeValue(string value, bool forAttribute) - { - if (value == null) - { - value = string.Empty; - } - Write(Utils.EscapeXML(value, forAttribute, true)); - } - - /// - /// A node can be serialized as RDF-Attribute, if it meets the following conditions: - ///
    - ///
  • is not array item - ///
  • don't has qualifier - ///
  • is no URI - ///
  • is no composite property - ///
- ///
- /// an XMPNode - /// Returns true if the node serialized as RDF-Attribute - private bool CanBeRDFAttrProp(XMPNode node) - { - return !node.HasQualifier() && !node.GetOptions().IsURI() && !node.GetOptions().IsCompositeProperty() && !XMPConstConstants.ArrayItemName.Equals(node.GetName()); - } - - /// Writes indents and automatically includes the baseindend from the options. - /// number of indents to write - /// forwards exception - private void WriteIndent(int times) - { - for (int i = options.GetBaseIndent() + times; i > 0; i--) - { - writer.Write(options.GetIndent()); - } - } - - /// Writes a char to the output. - /// a char - /// forwards writer exceptions - private void Write(int c) - { - writer.Write(c); - } - - /// Writes a String to the output. - /// a String - /// forwards writer exceptions - private void Write(string str) - { - writer.Write(str); - } - - /// Writes an amount of chars, mostly spaces - /// number of chars - /// a char - /// - private void WriteChars(int number, char c) - { - for (; number > 0; number--) - { - writer.Write(c); - } - } - - /// Writes a newline according to the options. - /// Forwards exception - private void WriteNewline() - { - writer.Write(options.GetNewline()); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Collections.Generic; +using System.IO; +using Com.Adobe.Xmp.Options; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// Serializes the XMPMeta-object using the standard RDF serialization format. + /// + /// Serializes the XMPMeta-object using the standard RDF serialization format. + /// The output is written to an OutputStream + /// according to the SerializeOptions. + /// + /// 11.07.2006 + public class XMPSerializerRDF + { + /// default padding + private const int DefaultPad = 2048; + + private const string PacketHeader = ""; + + /// The w/r is missing inbetween + private const string PacketTrailer = ""; + + private const string RdfXmpmetaStart = ""; + + private const string RdfRdfEnd = ""; + + private const string RdfSchemaStart = ""; + + private const string RdfStructStart = "a set of all rdf attribute qualifier + internal static readonly ICollection RdfAttrQualifier = new HashSet(Arrays.AsList(new string[] { XMPConstConstants.XmlLang, "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID" })); + + /// the metadata object to be serialized. + private XMPMetaImpl xmp; + + /// the output stream to serialize to + private CountOutputStream outputStream; + + /// this writer is used to do the actual serialization + private OutputStreamWriter writer; + + /// the stored serialization options + private SerializeOptions options; + + /// + /// the size of one unicode char, for UTF-8 set to 1 + /// (Note: only valid for ASCII chars lower than 0x80), + /// set to 2 in case of UTF-16 + /// + private int unicodeSize = 1; + + /// + /// the padding in the XMP Packet, or the length of the complete packet in + /// case of option exactPacketLength. + /// + private int padding; + + // UTF-8 + /// The actual serialization. + /// the metadata object to be serialized + /// outputStream the output stream to serialize to + /// the serialization options + /// If case of wrong options or any other serialization error. + public virtual void Serialize(XMPMeta xmp, OutputStream @out, SerializeOptions options) + { + try + { + outputStream = new CountOutputStream(@out); + writer = new OutputStreamWriter(outputStream, options.GetEncoding()); + this.xmp = (XMPMetaImpl)xmp; + this.options = options; + this.padding = options.GetPadding(); + writer = new OutputStreamWriter(outputStream, options.GetEncoding()); + CheckOptionsConsistence(); + // serializes the whole packet, but don't write the tail yet + // and flush to make sure that the written bytes are calculated correctly + string tailStr = SerializeAsRDF(); + writer.Flush(); + // adds padding + AddPadding(tailStr.Length); + // writes the tail + Write(tailStr); + writer.Flush(); + outputStream.Close(); + } + catch (IOException) + { + throw new XMPException("Error writing to the OutputStream", XMPErrorConstants.Unknown); + } + } + + /// Calculates the padding according to the options and write it to the stream. + /// the length of the tail string + /// thrown if packet size is to small to fit the padding + /// forwards writer errors + private void AddPadding(int tailLength) + { + if (options.GetExactPacketLength()) + { + // the string length is equal to the length of the UTF-8 encoding + int minSize = outputStream.GetBytesWritten() + tailLength * unicodeSize; + if (minSize > padding) + { + throw new XMPException("Can't fit into specified packet size", XMPErrorConstants.Badserialize); + } + padding -= minSize; + } + // Now the actual amount of padding to add. + // fix rest of the padding according to Unicode unit size. + padding /= unicodeSize; + int newlineLen = options.GetNewline().Length; + if (padding >= newlineLen) + { + padding -= newlineLen; + // Write this newline last. + while (padding >= (100 + newlineLen)) + { + WriteChars(100, ' '); + WriteNewline(); + padding -= (100 + newlineLen); + } + WriteChars(padding, ' '); + WriteNewline(); + } + else + { + WriteChars(padding, ' '); + } + } + + /// Checks if the supplied options are consistent. + /// Thrown if options are conflicting + protected internal virtual void CheckOptionsConsistence() + { + if (options.GetEncodeUTF16BE() | options.GetEncodeUTF16LE()) + { + unicodeSize = 2; + } + if (options.GetExactPacketLength()) + { + if (options.GetOmitPacketWrapper() | options.GetIncludeThumbnailPad()) + { + throw new XMPException("Inconsistent options for exact size serialize", XMPErrorConstants.Badoptions); + } + if ((options.GetPadding() & (unicodeSize - 1)) != 0) + { + throw new XMPException("Exact size must be a multiple of the Unicode element", XMPErrorConstants.Badoptions); + } + } + else + { + if (options.GetReadOnlyPacket()) + { + if (options.GetOmitPacketWrapper() | options.GetIncludeThumbnailPad()) + { + throw new XMPException("Inconsistent options for read-only packet", XMPErrorConstants.Badoptions); + } + padding = 0; + } + else + { + if (options.GetOmitPacketWrapper()) + { + if (options.GetIncludeThumbnailPad()) + { + throw new XMPException("Inconsistent options for non-packet serialize", XMPErrorConstants.Badoptions); + } + padding = 0; + } + else + { + if (padding == 0) + { + padding = DefaultPad * unicodeSize; + } + if (options.GetIncludeThumbnailPad()) + { + if (!xmp.DoesPropertyExist(XMPConstConstants.NsXmp, "Thumbnails")) + { + padding += 10000 * unicodeSize; + } + } + } + } + } + } + + /// Writes the (optional) packet header and the outer rdf-tags. + /// Returns the packet end processing instraction to be written after the padding. + /// Forwarded writer exceptions. + /// + private string SerializeAsRDF() + { + int level = 0; + // Write the packet header PI. + if (!options.GetOmitPacketWrapper()) + { + WriteIndent(level); + Write(PacketHeader); + WriteNewline(); + } + // Write the x:xmpmeta element's start tag. + if (!options.GetOmitXmpMetaElement()) + { + WriteIndent(level); + Write(RdfXmpmetaStart); + // Note: this flag can only be set by unit tests + if (!options.GetOmitVersionAttribute()) + { + Write(XMPMetaFactory.GetVersionInfo().GetMessage()); + } + Write("\">"); + WriteNewline(); + level++; + } + // Write the rdf:RDF start tag. + WriteIndent(level); + Write(RdfRdfStart); + WriteNewline(); + // Write all of the properties. + if (options.GetUseCanonicalFormat()) + { + SerializeCanonicalRDFSchemas(level); + } + else + { + SerializeCompactRDFSchemas(level); + } + // Write the rdf:RDF end tag. + WriteIndent(level); + Write(RdfRdfEnd); + WriteNewline(); + // Write the xmpmeta end tag. + if (!options.GetOmitXmpMetaElement()) + { + level--; + WriteIndent(level); + Write(RdfXmpmetaEnd); + WriteNewline(); + } + // Write the packet trailer PI into the tail string as UTF-8. + string tailStr = string.Empty; + if (!options.GetOmitPacketWrapper()) + { + for (level = options.GetBaseIndent(); level > 0; level--) + { + tailStr += options.GetIndent(); + } + tailStr += PacketTrailer; + tailStr += options.GetReadOnlyPacket() ? 'r' : 'w'; + tailStr += PacketTrailer2; + } + return tailStr; + } + + /// Serializes the metadata in pretty-printed manner. + /// indent level + /// Forwarded writer exceptions + /// + private void SerializeCanonicalRDFSchemas(int level) + { + if (xmp.GetRoot().GetChildrenLength() > 0) + { + StartOuterRDFDescription(xmp.GetRoot(), level); + for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); ) + { + XMPNode currSchema = (XMPNode)it.Next(); + SerializeCanonicalRDFSchema(currSchema, level); + } + EndOuterRDFDescription(level); + } + else + { + WriteIndent(level + 1); + Write(RdfSchemaStart); + // Special case an empty XMP object. + WriteTreeName(); + Write("/>"); + WriteNewline(); + } + } + + /// + private void WriteTreeName() + { + Write('"'); + string name = xmp.GetRoot().GetName(); + if (name != null) + { + AppendNodeValue(name, true); + } + Write('"'); + } + + /// Serializes the metadata in compact manner. + /// indent level to start with + /// Forwarded writer exceptions + /// + private void SerializeCompactRDFSchemas(int level) + { + // Begin the rdf:Description start tag. + WriteIndent(level + 1); + Write(RdfSchemaStart); + WriteTreeName(); + // Write all necessary xmlns attributes. + ICollection usedPrefixes = new HashSet(); + usedPrefixes.Add("xml"); + usedPrefixes.Add("rdf"); + for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext(); ) + { + XMPNode schema = (XMPNode)it.Next(); + DeclareUsedNamespaces(schema, usedPrefixes, level + 3); + } + // Write the top level "attrProps" and close the rdf:Description start tag. + bool allAreAttrs = true; + for (Iterator it_1 = xmp.GetRoot().IterateChildren(); it_1.HasNext(); ) + { + XMPNode schema = (XMPNode)it_1.Next(); + allAreAttrs &= SerializeCompactRDFAttrProps(schema, level + 2); + } + if (!allAreAttrs) + { + Write('>'); + WriteNewline(); + } + else + { + Write("/>"); + WriteNewline(); + return; + } + // ! Done if all properties in all schema are written as attributes. + // Write the remaining properties for each schema. + for (Iterator it_2 = xmp.GetRoot().IterateChildren(); it_2.HasNext(); ) + { + XMPNode schema = (XMPNode)it_2.Next(); + SerializeCompactRDFElementProps(schema, level + 2); + } + // Write the rdf:Description end tag. + WriteIndent(level + 1); + Write(RdfSchemaEnd); + WriteNewline(); + } + + /// Write each of the parent's simple unqualified properties as an attribute. + /// + /// Write each of the parent's simple unqualified properties as an attribute. Returns true if all + /// of the properties are written as attributes. + /// + /// the parent property node + /// the current indent level + /// Returns true if all properties can be rendered as RDF attribute. + /// + private bool SerializeCompactRDFAttrProps(XMPNode parentNode, int indent) + { + bool allAreAttrs = true; + for (Iterator it = parentNode.IterateChildren(); it.HasNext(); ) + { + XMPNode prop = (XMPNode)it.Next(); + if (CanBeRDFAttrProp(prop)) + { + WriteNewline(); + WriteIndent(indent); + Write(prop.GetName()); + Write("=\""); + AppendNodeValue(prop.GetValue(), true); + Write('"'); + } + else + { + allAreAttrs = false; + } + } + return allAreAttrs; + } + + /// + /// Recursively handles the "value" for a node that must be written as an RDF + /// property element. + /// + /// + /// Recursively handles the "value" for a node that must be written as an RDF + /// property element. It does not matter if it is a top level property, a + /// field of a struct, or an item of an array. The indent is that for the + /// property element. The patterns bwlow ignore attribute qualifiers such as + /// xml:lang, they don't affect the output form. + ///
+ ///
+        /// <ns:UnqualifiedStructProperty-1
+        /// ... The fields as attributes, if all are simple and unqualified
+        /// />
+        /// <ns:UnqualifiedStructProperty-2 rdf:parseType="Resource">
+        /// ... The fields as elements, if none are simple and unqualified
+        /// </ns:UnqualifiedStructProperty-2>
+        /// <ns:UnqualifiedStructProperty-3>
+        /// <rdf:Description
+        /// ... The simple and unqualified fields as attributes
+        /// >
+        /// ... The compound or qualified fields as elements
+        /// </rdf:Description>
+        /// </ns:UnqualifiedStructProperty-3>
+        /// <ns:UnqualifiedArrayProperty>
+        /// <rdf:Bag> or Seq or Alt
+        /// ... Array items as rdf:li elements, same forms as top level properties
+        /// </rdf:Bag>
+        /// </ns:UnqualifiedArrayProperty>
+        /// <ns:QualifiedProperty rdf:parseType="Resource">
+        /// <rdf:value> ... Property "value"
+        /// following the unqualified forms ... </rdf:value>
+        /// ... Qualifiers looking like named struct fields
+        /// </ns:QualifiedProperty>
+        /// 
+ ///
+ /// *** Consider numbered array items, but has compatibility problems. + /// Consider qualified form with rdf:Description and attributes. + ///
+ /// the parent node + /// the current indent level + /// Forwards writer exceptions + /// If qualifier and element fields are mixed. + private void SerializeCompactRDFElementProps(XMPNode parentNode, int indent) + { + for (Iterator it = parentNode.IterateChildren(); it.HasNext(); ) + { + XMPNode node = (XMPNode)it.Next(); + if (CanBeRDFAttrProp(node)) + { + continue; + } + bool emitEndTag = true; + bool indentEndTag = true; + // Determine the XML element name, write the name part of the start tag. Look over the + // qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute + // qualifiers at the same time. + string elemName = node.GetName(); + if (XMPConstConstants.ArrayItemName.Equals(elemName)) + { + elemName = "rdf:li"; + } + WriteIndent(indent); + Write('<'); + Write(elemName); + bool hasGeneralQualifiers = false; + bool hasRDFResourceQual = false; + for (Iterator iq = node.IterateQualifier(); iq.HasNext(); ) + { + XMPNode qualifier = (XMPNode)iq.Next(); + if (!RdfAttrQualifier.Contains(qualifier.GetName())) + { + hasGeneralQualifiers = true; + } + else + { + hasRDFResourceQual = "rdf:resource".Equals(qualifier.GetName()); + Write(' '); + Write(qualifier.GetName()); + Write("=\""); + AppendNodeValue(qualifier.GetValue(), true); + Write('"'); + } + } + // Process the property according to the standard patterns. + if (hasGeneralQualifiers) + { + SerializeCompactRDFGeneralQualifier(indent, node); + } + else + { + // This node has only attribute qualifiers. Emit as a property element. + if (!node.GetOptions().IsCompositeProperty()) + { + object[] result = SerializeCompactRDFSimpleProp(node); + emitEndTag = ((bool)result[0]); + indentEndTag = ((bool)result[1]); + } + else + { + if (node.GetOptions().IsArray()) + { + SerializeCompactRDFArrayProp(node, indent); + } + else + { + emitEndTag = SerializeCompactRDFStructProp(node, indent, hasRDFResourceQual); + } + } + } + // Emit the property element end tag. + if (emitEndTag) + { + if (indentEndTag) + { + WriteIndent(indent); + } + Write("'); + WriteNewline(); + } + } + } + + /// Serializes a simple property. + /// an XMPNode + /// Returns an array containing the flags emitEndTag and indentEndTag. + /// Forwards the writer exceptions. + private object[] SerializeCompactRDFSimpleProp(XMPNode node) + { + // This is a simple property. + bool emitEndTag = true; + bool indentEndTag = true; + if (node.GetOptions().IsURI()) + { + Write(" rdf:resource=\""); + AppendNodeValue(node.GetValue(), true); + Write("\"/>"); + WriteNewline(); + emitEndTag = false; + } + else + { + if (node.GetValue() == null || node.GetValue().Length == 0) + { + Write("/>"); + WriteNewline(); + emitEndTag = false; + } + else + { + Write('>'); + AppendNodeValue(node.GetValue(), false); + indentEndTag = false; + } + } + return new object[] { emitEndTag, indentEndTag }; + } + + /// Serializes an array property. + /// an XMPNode + /// the current indent level + /// Forwards the writer exceptions. + /// If qualifier and element fields are mixed. + private void SerializeCompactRDFArrayProp(XMPNode node, int indent) + { + // This is an array. + Write('>'); + WriteNewline(); + EmitRDFArrayTag(node, true, indent + 1); + if (node.GetOptions().IsArrayAltText()) + { + XMPNodeUtils.NormalizeLangArray(node); + } + SerializeCompactRDFElementProps(node, indent + 2); + EmitRDFArrayTag(node, false, indent + 1); + } + + /// Serializes a struct property. + /// an XMPNode + /// the current indent level + /// Flag if the element has resource qualifier + /// Returns true if an end flag shall be emitted. + /// Forwards the writer exceptions. + /// If qualifier and element fields are mixed. + private bool SerializeCompactRDFStructProp(XMPNode node, int indent, bool hasRDFResourceQual) + { + // This must be a struct. + bool hasAttrFields = false; + bool hasElemFields = false; + bool emitEndTag = true; + for (Iterator ic = node.IterateChildren(); ic.HasNext(); ) + { + XMPNode field = (XMPNode)ic.Next(); + if (CanBeRDFAttrProp(field)) + { + hasAttrFields = true; + } + else + { + hasElemFields = true; + } + if (hasAttrFields && hasElemFields) + { + break; + } + } + // No sense looking further. + if (hasRDFResourceQual && hasElemFields) + { + throw new XMPException("Can't mix rdf:resource qualifier and element fields", XMPErrorConstants.Badrdf); + } + if (!node.HasChildren()) + { + // Catch an empty struct as a special case. The case + // below would emit an empty + // XML element, which gets reparsed as a simple property + // with an empty value. + Write(" rdf:parseType=\"Resource\"/>"); + WriteNewline(); + emitEndTag = false; + } + else + { + if (!hasElemFields) + { + // All fields can be attributes, use the + // emptyPropertyElt form. + SerializeCompactRDFAttrProps(node, indent + 1); + Write("/>"); + WriteNewline(); + emitEndTag = false; + } + else + { + if (!hasAttrFields) + { + // All fields must be elements, use the + // parseTypeResourcePropertyElt form. + Write(" rdf:parseType=\"Resource\">"); + WriteNewline(); + SerializeCompactRDFElementProps(node, indent + 1); + } + else + { + // Have a mix of attributes and elements, use an inner rdf:Description. + Write('>'); + WriteNewline(); + WriteIndent(indent + 1); + Write(RdfStructStart); + SerializeCompactRDFAttrProps(node, indent + 2); + Write(">"); + WriteNewline(); + SerializeCompactRDFElementProps(node, indent + 1); + WriteIndent(indent + 1); + Write(RdfStructEnd); + WriteNewline(); + } + } + } + return emitEndTag; + } + + /// Serializes the general qualifier. + /// the root node of the subtree + /// the current indent level + /// Forwards all writer exceptions. + /// If qualifier and element fields are mixed. + private void SerializeCompactRDFGeneralQualifier(int indent, XMPNode node) + { + // The node has general qualifiers, ones that can't be + // attributes on a property element. + // Emit using the qualified property pseudo-struct form. The + // value is output by a call + // to SerializePrettyRDFProperty with emitAsRDFValue set. + Write(" rdf:parseType=\"Resource\">"); + WriteNewline(); + SerializeCanonicalRDFProperty(node, false, true, indent + 1); + for (Iterator iq = node.IterateQualifier(); iq.HasNext(); ) + { + XMPNode qualifier = (XMPNode)iq.Next(); + SerializeCanonicalRDFProperty(qualifier, false, false, indent + 1); + } + } + + /// + /// Serializes one schema with all contained properties in pretty-printed + /// manner.
+ /// Each schema's properties are written to a single + /// rdf:Description element. + ///
+ /// + /// Serializes one schema with all contained properties in pretty-printed + /// manner.
+ /// Each schema's properties are written to a single + /// rdf:Description element. All of the necessary namespaces are declared in + /// the rdf:Description element. The baseIndent is the base level for the + /// entire serialization, that of the x:xmpmeta element. An xml:lang + /// qualifier is written as an attribute of the property start tag, not by + /// itself forcing the qualified property form. + ///
+ ///
+        /// <rdf:Description rdf:about="TreeName" xmlns:ns="URI" ... >
+        /// ... The actual properties of the schema, see SerializePrettyRDFProperty
+        /// <!-- ns1:Alias is aliased to ns2:Actual -->  ... If alias comments are wanted
+        /// </rdf:Description>
+        /// 
+ ///
+ ///
+ /// a schema node + /// + /// Forwarded writer exceptions + /// + private void SerializeCanonicalRDFSchema(XMPNode schemaNode, int level) + { + // Write each of the schema's actual properties. + for (Iterator it = schemaNode.IterateChildren(); it.HasNext(); ) + { + XMPNode propNode = (XMPNode)it.Next(); + SerializeCanonicalRDFProperty(propNode, options.GetUseCanonicalFormat(), false, level + 2); + } + } + + /// Writes all used namespaces of the subtree in node to the output. + /// + /// Writes all used namespaces of the subtree in node to the output. + /// The subtree is recursivly traversed. + /// + /// the root node of the subtree + /// a set containing currently used prefixes + /// the current indent level + /// Forwards all writer exceptions. + private void DeclareUsedNamespaces(XMPNode node, ICollection usedPrefixes, int indent) + { + if (node.GetOptions().IsSchemaNode()) + { + // The schema node name is the URI, the value is the prefix. + string prefix = Runtime.Substring(node.GetValue(), 0, node.GetValue().Length - 1); + DeclareNamespace(prefix, node.GetName(), usedPrefixes, indent); + } + else + { + if (node.GetOptions().IsStruct()) + { + for (Iterator it = node.IterateChildren(); it.HasNext(); ) + { + XMPNode field = (XMPNode)it.Next(); + DeclareNamespace(field.GetName(), null, usedPrefixes, indent); + } + } + } + for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) + { + XMPNode child = (XMPNode)it_1.Next(); + DeclareUsedNamespaces(child, usedPrefixes, indent); + } + for (Iterator it_2 = node.IterateQualifier(); it_2.HasNext(); ) + { + XMPNode qualifier = (XMPNode)it_2.Next(); + DeclareNamespace(qualifier.GetName(), null, usedPrefixes, indent); + DeclareUsedNamespaces(qualifier, usedPrefixes, indent); + } + } + + /// Writes one namespace declaration to the output. + /// a namespace prefix (without colon) or a complete qname (when namespace == null) + /// the a namespace + /// a set containing currently used prefixes + /// the current indent level + /// Forwards all writer exceptions. + private void DeclareNamespace(string prefix, string @namespace, ICollection usedPrefixes, int indent) + { + if (@namespace == null) + { + // prefix contains qname, extract prefix and lookup namespace with prefix + QName qname = new QName(prefix); + if (qname.HasPrefix()) + { + prefix = qname.GetPrefix(); + // add colon for lookup + @namespace = XMPMetaFactory.GetSchemaRegistry().GetNamespaceURI(prefix + ":"); + // prefix w/o colon + DeclareNamespace(prefix, @namespace, usedPrefixes, indent); + } + else + { + return; + } + } + if (!usedPrefixes.Contains(prefix)) + { + WriteNewline(); + WriteIndent(indent); + Write("xmlns:"); + Write(prefix); + Write("=\""); + Write(@namespace); + Write('"'); + usedPrefixes.Add(prefix); + } + } + + /// Start the outer rdf:Description element, including all needed xmlns attributes. + /// + /// Start the outer rdf:Description element, including all needed xmlns attributes. + /// Leave the element open so that the compact form can add property attributes. + /// + /// If the writing to + private void StartOuterRDFDescription(XMPNode schemaNode, int level) + { + WriteIndent(level + 1); + Write(RdfSchemaStart); + WriteTreeName(); + ICollection usedPrefixes = new HashSet(); + usedPrefixes.Add("xml"); + usedPrefixes.Add("rdf"); + DeclareUsedNamespaces(schemaNode, usedPrefixes, level + 3); + Write('>'); + WriteNewline(); + } + + /// Write the end tag. + /// + private void EndOuterRDFDescription(int level) + { + WriteIndent(level + 1); + Write(RdfSchemaEnd); + WriteNewline(); + } + + /// Recursively handles the "value" for a node. + /// + /// Recursively handles the "value" for a node. It does not matter if it is a + /// top level property, a field of a struct, or an item of an array. The + /// indent is that for the property element. An xml:lang qualifier is written + /// as an attribute of the property start tag, not by itself forcing the + /// qualified property form. The patterns below mostly ignore attribute + /// qualifiers like xml:lang. Except for the one struct case, attribute + /// qualifiers don't affect the output form. + ///
+ ///
+        /// <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty>
+        /// <ns:UnqualifiedStructProperty> (If no rdf:resource qualifier)
+        /// <rdf:Description>
+        /// ... Fields, same forms as top level properties
+        /// </rdf:Description>
+        /// </ns:UnqualifiedStructProperty>
+        /// <ns:ResourceStructProperty rdf:resource="URI"
+        /// ... Fields as attributes
+        /// >
+        /// <ns:UnqualifiedArrayProperty>
+        /// <rdf:Bag> or Seq or Alt
+        /// ... Array items as rdf:li elements, same forms as top level properties
+        /// </rdf:Bag>
+        /// </ns:UnqualifiedArrayProperty>
+        /// <ns:QualifiedProperty>
+        /// <rdf:Description>
+        /// <rdf:value> ... Property "value" following the unqualified
+        /// forms ... </rdf:value>
+        /// ... Qualifiers looking like named struct fields
+        /// </rdf:Description>
+        /// </ns:QualifiedProperty>
+        /// 
+ ///
+ ///
+ /// the property node + /// property shall be rendered as attribute rather than tag + /// + /// use canonical form with inner description tag or + /// the compact form with rdf:ParseType="resource" attribute. + /// + /// the current indent level + /// Forwards all writer exceptions. + /// If "rdf:resource" and general qualifiers are mixed. + private void SerializeCanonicalRDFProperty(XMPNode node, bool useCanonicalRDF, bool emitAsRDFValue, int indent) + { + bool emitEndTag = true; + bool indentEndTag = true; + // Determine the XML element name. Open the start tag with the name and + // attribute qualifiers. + string elemName = node.GetName(); + if (emitAsRDFValue) + { + elemName = "rdf:value"; + } + else + { + if (XMPConstConstants.ArrayItemName.Equals(elemName)) + { + elemName = "rdf:li"; + } + } + WriteIndent(indent); + Write('<'); + Write(elemName); + bool hasGeneralQualifiers = false; + bool hasRDFResourceQual = false; + for (Iterator it = node.IterateQualifier(); it.HasNext(); ) + { + XMPNode qualifier = (XMPNode)it.Next(); + if (!RdfAttrQualifier.Contains(qualifier.GetName())) + { + hasGeneralQualifiers = true; + } + else + { + hasRDFResourceQual = "rdf:resource".Equals(qualifier.GetName()); + if (!emitAsRDFValue) + { + Write(' '); + Write(qualifier.GetName()); + Write("=\""); + AppendNodeValue(qualifier.GetValue(), true); + Write('"'); + } + } + } + // Process the property according to the standard patterns. + if (hasGeneralQualifiers && !emitAsRDFValue) + { + // This node has general, non-attribute, qualifiers. Emit using the + // qualified property form. + // ! The value is output by a recursive call ON THE SAME NODE with + // emitAsRDFValue set. + if (hasRDFResourceQual) + { + throw new XMPException("Can't mix rdf:resource and general qualifiers", XMPErrorConstants.Badrdf); + } + // Change serialization to canonical format with inner rdf:Description-tag + // depending on option + if (useCanonicalRDF) + { + Write(">"); + WriteNewline(); + indent++; + WriteIndent(indent); + Write(RdfStructStart); + Write(">"); + } + else + { + Write(" rdf:parseType=\"Resource\">"); + } + WriteNewline(); + SerializeCanonicalRDFProperty(node, useCanonicalRDF, true, indent + 1); + for (Iterator it_1 = node.IterateQualifier(); it_1.HasNext(); ) + { + XMPNode qualifier = (XMPNode)it_1.Next(); + if (!RdfAttrQualifier.Contains(qualifier.GetName())) + { + SerializeCanonicalRDFProperty(qualifier, useCanonicalRDF, false, indent + 1); + } + } + if (useCanonicalRDF) + { + WriteIndent(indent); + Write(RdfStructEnd); + WriteNewline(); + indent--; + } + } + else + { + // This node has no general qualifiers. Emit using an unqualified form. + if (!node.GetOptions().IsCompositeProperty()) + { + // This is a simple property. + if (node.GetOptions().IsURI()) + { + Write(" rdf:resource=\""); + AppendNodeValue(node.GetValue(), true); + Write("\"/>"); + WriteNewline(); + emitEndTag = false; + } + else + { + if (node.GetValue() == null || string.Empty.Equals(node.GetValue())) + { + Write("/>"); + WriteNewline(); + emitEndTag = false; + } + else + { + Write('>'); + AppendNodeValue(node.GetValue(), false); + indentEndTag = false; + } + } + } + else + { + if (node.GetOptions().IsArray()) + { + // This is an array. + Write('>'); + WriteNewline(); + EmitRDFArrayTag(node, true, indent + 1); + if (node.GetOptions().IsArrayAltText()) + { + XMPNodeUtils.NormalizeLangArray(node); + } + for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) + { + XMPNode child = (XMPNode)it_1.Next(); + SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 2); + } + EmitRDFArrayTag(node, false, indent + 1); + } + else + { + if (!hasRDFResourceQual) + { + // This is a "normal" struct, use the rdf:parseType="Resource" form. + if (!node.HasChildren()) + { + // Change serialization to canonical format with inner rdf:Description-tag + // if option is set + if (useCanonicalRDF) + { + Write(">"); + WriteNewline(); + WriteIndent(indent + 1); + Write(RdfEmptyStruct); + } + else + { + Write(" rdf:parseType=\"Resource\"/>"); + emitEndTag = false; + } + WriteNewline(); + } + else + { + // Change serialization to canonical format with inner rdf:Description-tag + // if option is set + if (useCanonicalRDF) + { + Write(">"); + WriteNewline(); + indent++; + WriteIndent(indent); + Write(RdfStructStart); + Write(">"); + } + else + { + Write(" rdf:parseType=\"Resource\">"); + } + WriteNewline(); + for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) + { + XMPNode child = (XMPNode)it_1.Next(); + SerializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 1); + } + if (useCanonicalRDF) + { + WriteIndent(indent); + Write(RdfStructEnd); + WriteNewline(); + indent--; + } + } + } + else + { + // This is a struct with an rdf:resource attribute, use the + // "empty property element" form. + for (Iterator it_1 = node.IterateChildren(); it_1.HasNext(); ) + { + XMPNode child = (XMPNode)it_1.Next(); + if (!CanBeRDFAttrProp(child)) + { + throw new XMPException("Can't mix rdf:resource and complex fields", XMPErrorConstants.Badrdf); + } + WriteNewline(); + WriteIndent(indent + 1); + Write(' '); + Write(child.GetName()); + Write("=\""); + AppendNodeValue(child.GetValue(), true); + Write('"'); + } + Write("/>"); + WriteNewline(); + emitEndTag = false; + } + } + } + } + // Emit the property element end tag. + if (emitEndTag) + { + if (indentEndTag) + { + WriteIndent(indent); + } + Write("'); + WriteNewline(); + } + } + + /// Writes the array start and end tags. + /// an array node + /// flag if its the start or end tag + /// the current indent level + /// forwards writer exceptions + private void EmitRDFArrayTag(XMPNode arrayNode, bool isStartTag, int indent) + { + if (isStartTag || arrayNode.HasChildren()) + { + WriteIndent(indent); + Write(isStartTag ? ""); + } + else + { + Write(">"); + } + WriteNewline(); + } + } + + /// Serializes the node value in XML encoding. + /// + /// Serializes the node value in XML encoding. Its used for tag bodies and + /// attributes. Note: The attribute is always limited by quotes, + /// thats why &apos; is never serialized. Note: + /// Control chars are written unescaped, but if the user uses others than tab, LF + /// and CR the resulting XML will become invalid. + /// + /// the value of the node + /// flag if value is an attribute value + /// + private void AppendNodeValue(string value, bool forAttribute) + { + if (value == null) + { + value = string.Empty; + } + Write(Utils.EscapeXML(value, forAttribute, true)); + } + + /// + /// A node can be serialized as RDF-Attribute, if it meets the following conditions: + ///
    + ///
  • is not array item + ///
  • don't has qualifier + ///
  • is no URI + ///
  • is no composite property + ///
+ ///
+ /// an XMPNode + /// Returns true if the node serialized as RDF-Attribute + private bool CanBeRDFAttrProp(XMPNode node) + { + return !node.HasQualifier() && !node.GetOptions().IsURI() && !node.GetOptions().IsCompositeProperty() && !XMPConstConstants.ArrayItemName.Equals(node.GetName()); + } + + /// Writes indents and automatically includes the baseindend from the options. + /// number of indents to write + /// forwards exception + private void WriteIndent(int times) + { + for (int i = options.GetBaseIndent() + times; i > 0; i--) + { + writer.Write(options.GetIndent()); + } + } + + /// Writes a char to the output. + /// a char + /// forwards writer exceptions + private void Write(int c) + { + writer.Write(c); + } + + /// Writes a String to the output. + /// a String + /// forwards writer exceptions + private void Write(string str) + { + writer.Write(str); + } + + /// Writes an amount of chars, mostly spaces + /// number of chars + /// a char + /// + private void WriteChars(int number, char c) + { + for (; number > 0; number--) + { + writer.Write(c); + } + } + + /// Writes a newline according to the options. + /// Forwards exception + private void WriteNewline() + { + writer.Write(options.GetNewline()); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPUtilsImpl.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPUtilsImpl.cs index 18f68c97e..94d0ce805 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPUtilsImpl.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/XMPUtilsImpl.cs @@ -1,1105 +1,1105 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Diagnostics; -using System.Text; -using Com.Adobe.Xmp.Impl.Xpath; -using Com.Adobe.Xmp.Options; -using Com.Adobe.Xmp.Properties; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl -{ - /// 11.08.2006 - public class XMPUtilsImpl : XMPConst - { - private const int UckNormal = 0; - - private const int UckSpace = 1; - - private const int UckComma = 2; - - private const int UckSemicolon = 3; - - private const int UckQuote = 4; - - private const int UckControl = 5; - - /// Private constructor, as - private XMPUtilsImpl() - { - } - - // EMPTY - /// - /// The XMP object containing the array to be catenated. - /// - /// The schema namespace URI for the array. Must not be null or - /// the empty string. - /// - /// - /// The name of the array. May be a general path expression, must - /// not be null or the empty string. Each item in the array must - /// be a simple string value. - /// - /// - /// The string to be used to separate the items in the catenated - /// string. Defaults to "; ", ASCII semicolon and space - /// (U+003B, U+0020). - /// - /// - /// The characters to be used as quotes around array items that - /// contain a separator. Defaults to '"' - /// - /// Option flag to control the catenation. - /// Returns the string containing the catenated array items. - /// Forwards the Exceptions from the metadata processing - public static string CatenateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string separator, string quotes, bool allowCommas) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - ParameterAsserts.AssertImplementation(xmp); - if (separator == null || separator.Length == 0) - { - separator = "; "; - } - if (quotes == null || quotes.Length == 0) - { - quotes = "\""; - } - XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; - XMPNode arrayNode = null; - XMPNode currItem = null; - // Return an empty result if the array does not exist, - // hurl if it isn't the right form. - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); - arrayNode = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), arrayPath, false, null); - if (arrayNode == null) - { - return string.Empty; - } - else - { - if (!arrayNode.GetOptions().IsArray() || arrayNode.GetOptions().IsArrayAlternate()) - { - throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badparam); - } - } - // Make sure the separator is OK. - CheckSeparator(separator); - // Make sure the open and close quotes are a legitimate pair. - char openQuote = quotes[0]; - char closeQuote = CheckQuotes(quotes, openQuote); - // Build the result, quoting the array items, adding separators. - // Hurl if any item isn't simple. - StringBuilder catinatedString = new StringBuilder(); - for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) - { - currItem = (XMPNode)it.Next(); - if (currItem.GetOptions().IsCompositeProperty()) - { - throw new XMPException("Array items must be simple", XMPErrorConstants.Badparam); - } - string str = ApplyQuotes(currItem.GetValue(), openQuote, closeQuote, allowCommas); - catinatedString.Append(str); - if (it.HasNext()) - { - catinatedString.Append(separator); - } - } - return catinatedString.ToString(); - } - - /// - /// see - /// - /// - /// The XMP object containing the array to be updated. - /// - /// The schema namespace URI for the array. Must not be null or - /// the empty string. - /// - /// - /// The name of the array. May be a general path expression, must - /// not be null or the empty string. Each item in the array must - /// be a simple string value. - /// - /// The string to be separated into the array items. - /// Option flags to control the separation. - /// Flag if commas shall be preserved - /// Forwards the Exceptions from the metadata processing - public static void SeparateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) - { - ParameterAsserts.AssertSchemaNS(schemaNS); - ParameterAsserts.AssertArrayName(arrayName); - if (catedStr == null) - { - throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); - } - ParameterAsserts.AssertImplementation(xmp); - XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; - // Keep a zero value, has special meaning below. - XMPNode arrayNode = SeparateFindCreateArray(schemaNS, arrayName, arrayOptions, xmpImpl); - // Extract the item values one at a time, until the whole input string is done. - string itemValue; - int itemStart; - int itemEnd; - int nextKind = UckNormal; - int charKind = UckNormal; - char ch = (char)0; - char nextChar = (char)0; - itemEnd = 0; - int endPos = catedStr.Length; - while (itemEnd < endPos) - { - // Skip any leading spaces and separation characters. Always skip commas here. - // They can be kept when within a value, but not when alone between values. - for (itemStart = itemEnd; itemStart < endPos; itemStart++) - { - ch = catedStr[itemStart]; - charKind = ClassifyCharacter(ch); - if (charKind == UckNormal || charKind == UckQuote) - { - break; - } - } - if (itemStart >= endPos) - { - break; - } - if (charKind != UckQuote) - { - // This is not a quoted value. Scan for the end, create an array - // item from the substring. - for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) - { - ch = catedStr[itemEnd]; - charKind = ClassifyCharacter(ch); - if (charKind == UckNormal || charKind == UckQuote || (charKind == UckComma && preserveCommas)) - { - continue; - } - else - { - if (charKind != UckSpace) - { - break; - } - else - { - if ((itemEnd + 1) < endPos) - { - ch = catedStr[itemEnd + 1]; - nextKind = ClassifyCharacter(ch); - if (nextKind == UckNormal || nextKind == UckQuote || (nextKind == UckComma && preserveCommas)) - { - continue; - } - } - } - } - // Anything left? - break; - } - // Have multiple spaces, or a space followed by a - // separator. - itemValue = Runtime.Substring(catedStr, itemStart, itemEnd); - } - else - { - // Accumulate quoted values into a local string, undoubling - // internal quotes that - // match the surrounding quotes. Do not undouble "unmatching" - // quotes. - char openQuote = ch; - char closeQuote = GetClosingQuote(openQuote); - itemStart++; - // Skip the opening quote; - itemValue = string.Empty; - for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) - { - ch = catedStr[itemEnd]; - charKind = ClassifyCharacter(ch); - if (charKind != UckQuote || !IsSurroundingQuote(ch, openQuote, closeQuote)) - { - // This is not a matching quote, just append it to the - // item value. - itemValue += ch; - } - else - { - // This is a "matching" quote. Is it doubled, or the - // final closing quote? - // Tolerate various edge cases like undoubled opening - // (non-closing) quotes, - // or end of input. - if ((itemEnd + 1) < endPos) - { - nextChar = catedStr[itemEnd + 1]; - nextKind = ClassifyCharacter(nextChar); - } - else - { - nextKind = UckSemicolon; - nextChar = (char)0x3B; - } - if (ch == nextChar) - { - // This is doubled, copy it and skip the double. - itemValue += ch; - // Loop will add in charSize. - itemEnd++; - } - else - { - if (!IsClosingingQuote(ch, openQuote, closeQuote)) - { - // This is an undoubled, non-closing quote, copy it. - itemValue += ch; - } - else - { - // This is an undoubled closing quote, skip it and - // exit the loop. - itemEnd++; - break; - } - } - } - } - } - // Add the separated item to the array. - // Keep a matching old value in case it had separators. - int foundIndex = -1; - for (int oldChild = 1; oldChild <= arrayNode.GetChildrenLength(); oldChild++) - { - if (itemValue.Equals(arrayNode.GetChild(oldChild).GetValue())) - { - foundIndex = oldChild; - break; - } - } - XMPNode newItem = null; - if (foundIndex < 0) - { - newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); - arrayNode.AddChild(newItem); - } - } - } - - /// Utility to find or create the array used by separateArrayItems(). - /// a the namespace fo the array - /// the name of the array - /// the options for the array if newly created - /// the xmp object - /// Returns the array node. - /// Forwards exceptions - private static XMPNode SeparateFindCreateArray(string schemaNS, string arrayName, PropertyOptions arrayOptions, XMPMetaImpl xmp) - { - arrayOptions = XMPNodeUtils.VerifySetOptions(arrayOptions, null); - if (!arrayOptions.IsOnlyArrayOptions()) - { - throw new XMPException("Options can only provide array form", XMPErrorConstants.Badoptions); - } - // Find the array node, make sure it is OK. Move the current children - // aside, to be readded later if kept. - XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); - XMPNode arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, false, null); - if (arrayNode != null) - { - // The array exists, make sure the form is compatible. Zero - // arrayForm means take what exists. - PropertyOptions arrayForm = arrayNode.GetOptions(); - if (!arrayForm.IsArray() || arrayForm.IsArrayAlternate()) - { - throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badxpath); - } - if (arrayOptions.EqualArrayTypes(arrayForm)) - { - throw new XMPException("Mismatch of specified and existing array form", XMPErrorConstants.Badxpath); - } - } - else - { - // *** Right error? - // The array does not exist, try to create it. - // don't modify the options handed into the method - arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions.SetArray(true)); - if (arrayNode == null) - { - throw new XMPException("Failed to create named array", XMPErrorConstants.Badxpath); - } - } - return arrayNode; - } - - /// - /// The XMP object containing the properties to be removed. - /// - /// Optional schema namespace URI for the properties to be - /// removed. - /// - /// Optional path expression for the property to be removed. - /// - /// Option flag to control the deletion: do internal properties in - /// addition to external properties. - /// - /// - /// Option flag to control the deletion: Include aliases in the - /// "named schema" case above. - /// - /// If metadata processing fails - public static void RemoveProperties(XMPMeta xmp, string schemaNS, string propName, bool doAllProperties, bool includeAliases) - { - ParameterAsserts.AssertImplementation(xmp); - XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; - if (propName != null && propName.Length > 0) - { - // Remove just the one indicated property. This might be an alias, - // the named schema might not actually exist. So don't lookup the - // schema node. - if (schemaNS == null || schemaNS.Length == 0) - { - throw new XMPException("Property name requires schema namespace", XMPErrorConstants.Badparam); - } - XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); - XMPNode propNode = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), expPath, false, null); - if (propNode != null) - { - if (doAllProperties || !Utils.IsInternalProperty(expPath.GetSegment(XMPPath.StepSchema).GetName(), expPath.GetSegment(XMPPath.StepRootProp).GetName())) - { - XMPNode parent = propNode.GetParent(); - parent.RemoveChild(propNode); - if (parent.GetOptions().IsSchemaNode() && !parent.HasChildren()) - { - // remove empty schema node - parent.GetParent().RemoveChild(parent); - } - } - } - } - else - { - if (schemaNS != null && schemaNS.Length > 0) - { - // Remove all properties from the named schema. Optionally include - // aliases, in which case - // there might not be an actual schema node. - // XMP_NodePtrPos schemaPos; - XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmpImpl.GetRoot(), schemaNS, false); - if (schemaNode != null) - { - if (RemoveSchemaChildren(schemaNode, doAllProperties)) - { - xmpImpl.GetRoot().RemoveChild(schemaNode); - } - } - if (includeAliases) - { - // We're removing the aliases also. Look them up by their - // namespace prefix. - // But that takes more code and the extra speed isn't worth it. - // Lookup the XMP node - // from the alias, to make sure the actual exists. - XMPAliasInfo[] aliases = XMPMetaFactory.GetSchemaRegistry().FindAliases(schemaNS); - for (int i = 0; i < aliases.Length; i++) - { - XMPAliasInfo info = aliases[i]; - XMPPath path = XMPPathParser.ExpandXPath(info.GetNamespace(), info.GetPropName()); - XMPNode actualProp = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), path, false, null); - if (actualProp != null) - { - XMPNode parent = actualProp.GetParent(); - parent.RemoveChild(actualProp); - } - } - } - } - else - { - // Remove all appropriate properties from all schema. In this case - // we don't have to be - // concerned with aliases, they are handled implicitly from the - // actual properties. - for (Iterator it = xmpImpl.GetRoot().IterateChildren(); it.HasNext(); ) - { - XMPNode schema = (XMPNode)it.Next(); - if (RemoveSchemaChildren(schema, doAllProperties)) - { - it.Remove(); - } - } - } - } - } - - /// - /// The source XMP object. - /// The destination XMP object. - /// Do internal properties in addition to external properties. - /// Replace the values of existing properties. - /// Delete destination values if source property is empty. - /// Forwards the Exceptions from the metadata processing - public static void AppendProperties(XMPMeta source, XMPMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) - { - ParameterAsserts.AssertImplementation(source); - ParameterAsserts.AssertImplementation(destination); - XMPMetaImpl src = (XMPMetaImpl)source; - XMPMetaImpl dest = (XMPMetaImpl)destination; - for (Iterator it = src.GetRoot().IterateChildren(); it.HasNext(); ) - { - XMPNode sourceSchema = (XMPNode)it.Next(); - // Make sure we have a destination schema node - XMPNode destSchema = XMPNodeUtils.FindSchemaNode(dest.GetRoot(), sourceSchema.GetName(), false); - bool createdSchema = false; - if (destSchema == null) - { - destSchema = new XMPNode(sourceSchema.GetName(), sourceSchema.GetValue(), new PropertyOptions().SetSchemaNode(true)); - dest.GetRoot().AddChild(destSchema); - createdSchema = true; - } - // Process the source schema's children. - for (Iterator ic = sourceSchema.IterateChildren(); ic.HasNext(); ) - { - XMPNode sourceProp = (XMPNode)ic.Next(); - if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.GetName(), sourceProp.GetName())) - { - AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); - } - } - if (!destSchema.HasChildren() && (createdSchema || deleteEmptyValues)) - { - // Don't create an empty schema / remove empty schema. - dest.GetRoot().RemoveChild(destSchema); - } - } - } - - /// - /// Remove all schema children according to the flag - /// doAllProperties. - /// - /// - /// Remove all schema children according to the flag - /// doAllProperties. Empty schemas are automatically remove - /// by XMPNode - /// - /// a schema node - /// flag if all properties or only externals shall be removed. - /// Returns true if the schema is empty after the operation. - private static bool RemoveSchemaChildren(XMPNode schemaNode, bool doAllProperties) - { - for (Iterator it = schemaNode.IterateChildren(); it.HasNext(); ) - { - XMPNode currProp = (XMPNode)it.Next(); - if (doAllProperties || !Utils.IsInternalProperty(schemaNode.GetName(), currProp.GetName())) - { - it.Remove(); - } - } - return !schemaNode.HasChildren(); - } - - /// - /// The destination XMP object. - /// the source node - /// the parent of the destination node - /// Replace the values of existing properties. - /// - /// flag if properties with empty values should be deleted - /// in the destination object. - /// - /// - private static void AppendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent, bool replaceOldValues, bool deleteEmptyValues) - { - XMPNode destNode = XMPNodeUtils.FindChildNode(destParent, sourceNode.GetName(), false); - bool valueIsEmpty = false; - if (deleteEmptyValues) - { - valueIsEmpty = sourceNode.GetOptions().IsSimple() ? sourceNode.GetValue() == null || sourceNode.GetValue().Length == 0 : !sourceNode.HasChildren(); - } - if (deleteEmptyValues && valueIsEmpty) - { - if (destNode != null) - { - destParent.RemoveChild(destNode); - } - } - else - { - if (destNode == null) - { - // The one easy case, the destination does not exist. - destParent.AddChild((XMPNode)sourceNode.Clone()); - } - else - { - if (replaceOldValues) - { - // The destination exists and should be replaced. - destXMP.SetNode(destNode, sourceNode.GetValue(), sourceNode.GetOptions(), true); - destParent.RemoveChild(destNode); - destNode = (XMPNode)sourceNode.Clone(); - destParent.AddChild(destNode); - } - else - { - // The destination exists and is not totally replaced. Structs and - // arrays are merged. - PropertyOptions sourceForm = sourceNode.GetOptions(); - PropertyOptions destForm = destNode.GetOptions(); - if (sourceForm != destForm) - { - return; - } - if (sourceForm.IsStruct()) - { - // To merge a struct process the fields recursively. E.g. add simple missing fields. - // The recursive call to AppendSubtree will handle deletion for fields with empty - // values. - for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) - { - XMPNode sourceField = (XMPNode)it.Next(); - AppendSubtree(destXMP, sourceField, destNode, replaceOldValues, deleteEmptyValues); - if (deleteEmptyValues && !destNode.HasChildren()) - { - destParent.RemoveChild(destNode); - } - } - } - else - { - if (sourceForm.IsArrayAltText()) - { - // Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. - // Make a special check for deletion of empty values. Meaningful in AltText arrays - // because the "xml:lang" qualifier provides unambiguous source/dest correspondence. - for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) - { - XMPNode sourceItem = (XMPNode)it.Next(); - if (!sourceItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(sourceItem.GetQualifier(1).GetName())) - { - continue; - } - int destIndex = XMPNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).GetValue()); - if (deleteEmptyValues && (sourceItem.GetValue() == null || sourceItem.GetValue().Length == 0)) - { - if (destIndex != -1) - { - destNode.RemoveChild(destIndex); - if (!destNode.HasChildren()) - { - destParent.RemoveChild(destNode); - } - } - } - else - { - if (destIndex == -1) - { - // Not replacing, keep the existing item. - if (!XMPConstConstants.XDefault.Equals(sourceItem.GetQualifier(1).GetValue()) || !destNode.HasChildren()) - { - sourceItem.CloneSubtree(destNode); - } - else - { - XMPNode destItem = new XMPNode(sourceItem.GetName(), sourceItem.GetValue(), sourceItem.GetOptions()); - sourceItem.CloneSubtree(destItem); - destNode.AddChild(1, destItem); - } - } - } - } - } - else - { - if (sourceForm.IsArray()) - { - // Merge other arrays by item values. Don't worry about order or duplicates. Source - // items with empty values do not cause deletion, that conflicts horribly with - // merging. - for (Iterator @is = sourceNode.IterateChildren(); @is.HasNext(); ) - { - XMPNode sourceItem = (XMPNode)@is.Next(); - bool match = false; - for (Iterator id = destNode.IterateChildren(); id.HasNext(); ) - { - XMPNode destItem = (XMPNode)id.Next(); - if (ItemValuesMatch(sourceItem, destItem)) - { - match = true; - } - } - if (!match) - { - destNode = (XMPNode)sourceItem.Clone(); - destParent.AddChild(destNode); - } - } - } - } - } - } - } - } - } - - /// Compares two nodes including its children and qualifier. - /// an XMPNode - /// an XMPNode - /// Returns true if the nodes are equal, false otherwise. - /// Forwards exceptions to the calling method. - private static bool ItemValuesMatch(XMPNode leftNode, XMPNode rightNode) - { - PropertyOptions leftForm = leftNode.GetOptions(); - PropertyOptions rightForm = rightNode.GetOptions(); - if (leftForm.Equals(rightForm)) - { - return false; - } - if (leftForm.GetOptions() == 0) - { - // Simple nodes, check the values and xml:lang qualifiers. - if (!leftNode.GetValue().Equals(rightNode.GetValue())) - { - return false; - } - if (leftNode.GetOptions().GetHasLanguage() != rightNode.GetOptions().GetHasLanguage()) - { - return false; - } - if (leftNode.GetOptions().GetHasLanguage() && !leftNode.GetQualifier(1).GetValue().Equals(rightNode.GetQualifier(1).GetValue())) - { - return false; - } - } - else - { - if (leftForm.IsStruct()) - { - // Struct nodes, see if all fields match, ignoring order. - if (leftNode.GetChildrenLength() != rightNode.GetChildrenLength()) - { - return false; - } - for (Iterator it = leftNode.IterateChildren(); it.HasNext(); ) - { - XMPNode leftField = (XMPNode)it.Next(); - XMPNode rightField = XMPNodeUtils.FindChildNode(rightNode, leftField.GetName(), false); - if (rightField == null || !ItemValuesMatch(leftField, rightField)) - { - return false; - } - } - } - else - { - // Array nodes, see if the "leftNode" values are present in the - // "rightNode", ignoring order, duplicates, - // and extra values in the rightNode-> The rightNode is the - // destination for AppendProperties. - Debug.Assert(leftForm.IsArray()); - for (Iterator il = leftNode.IterateChildren(); il.HasNext(); ) - { - XMPNode leftItem = (XMPNode)il.Next(); - bool match = false; - for (Iterator ir = rightNode.IterateChildren(); ir.HasNext(); ) - { - XMPNode rightItem = (XMPNode)ir.Next(); - if (ItemValuesMatch(leftItem, rightItem)) - { - match = true; - break; - } - } - if (!match) - { - return false; - } - } - } - } - return true; - } - - // All of the checks passed. - /// Make sure the separator is OK. - /// - /// Make sure the separator is OK. It must be one semicolon surrounded by - /// zero or more spaces. Any of the recognized semicolons or spaces are - /// allowed. - /// - /// - /// - private static void CheckSeparator(string separator) - { - bool haveSemicolon = false; - for (int i = 0; i < separator.Length; i++) - { - int charKind = ClassifyCharacter(separator[i]); - if (charKind == UckSemicolon) - { - if (haveSemicolon) - { - throw new XMPException("Separator can have only one semicolon", XMPErrorConstants.Badparam); - } - haveSemicolon = true; - } - else - { - if (charKind != UckSpace) - { - throw new XMPException("Separator can have only spaces and one semicolon", XMPErrorConstants.Badparam); - } - } - } - if (!haveSemicolon) - { - throw new XMPException("Separator must have one semicolon", XMPErrorConstants.Badparam); - } - } - - /// - /// Make sure the open and close quotes are a legitimate pair and return the - /// correct closing quote or an exception. - /// - /// opened and closing quote in a string - /// the open quote - /// Returns a corresponding closing quote. - /// - private static char CheckQuotes(string quotes, char openQuote) - { - char closeQuote; - int charKind = ClassifyCharacter(openQuote); - if (charKind != UckQuote) - { - throw new XMPException("Invalid quoting character", XMPErrorConstants.Badparam); - } - if (quotes.Length == 1) - { - closeQuote = openQuote; - } - else - { - closeQuote = quotes[1]; - charKind = ClassifyCharacter(closeQuote); - if (charKind != UckQuote) - { - throw new XMPException("Invalid quoting character", XMPErrorConstants.Badparam); - } - } - if (closeQuote != GetClosingQuote(openQuote)) - { - throw new XMPException("Mismatched quote pair", XMPErrorConstants.Badparam); - } - return closeQuote; - } - - /// - /// Classifies the character into normal chars, spaces, semicola, quotes, - /// control chars. - /// - /// a char - /// Return the character kind. - private static int ClassifyCharacter(char ch) - { - if (Spaces.IndexOf(ch) >= 0 || (unchecked((int)(0x2000)) <= ch && ch <= unchecked((int)(0x200B)))) - { - return UckSpace; - } - else - { - if (Commas.IndexOf(ch) >= 0) - { - return UckComma; - } - else - { - if (Semicola.IndexOf(ch) >= 0) - { - return UckSemicolon; - } - else - { - if (Quotes.IndexOf(ch) >= 0 || (unchecked((int)(0x3008)) <= ch && ch <= unchecked((int)(0x300F))) || (unchecked((int)(0x2018)) <= ch && ch <= unchecked((int)(0x201F)))) - { - return UckQuote; - } - else - { - if (ch < unchecked((int)(0x0020)) || Controls.IndexOf(ch) >= 0) - { - return UckControl; - } - else - { - // Assume typical case. - return UckNormal; - } - } - } - } - } - } - - /// the open quote char - /// Returns the matching closing quote for an open quote. - private static char GetClosingQuote(char openQuote) - { - switch (openQuote) - { - case (char)0x0022: - { - return (char)0x0022; - } - - case (char)0x00AB: - { - // ! U+0022 is both opening and closing. - // Not interpreted as brackets anymore - // case 0x005B: - // return 0x005D; - return (char)0x00BB; - } - - case (char)0x00BB: - { - // ! U+00AB and U+00BB are reversible. - return (char)0x00AB; - } - - case (char)0x2015: - { - return (char)0x2015; - } - - case (char)0x2018: - { - // ! U+2015 is both opening and closing. - return (char)0x2019; - } - - case (char)0x201A: - { - return (char)0x201B; - } - - case (char)0x201C: - { - return (char)0x201D; - } - - case (char)0x201E: - { - return (char)0x201F; - } - - case (char)0x2039: - { - return (char)0x203A; - } - - case (char)0x203A: - { - // ! U+2039 and U+203A are reversible. - return (char)0x2039; - } - - case (char)0x3008: - { - return (char)0x3009; - } - - case (char)0x300A: - { - return (char)0x300B; - } - - case (char)0x300C: - { - return (char)0x300D; - } - - case (char)0x300E: - { - return (char)0x300F; - } - - case (char)0x301D: - { - return (char)0x301F; - } - - default: - { - // ! U+301E also closes U+301D. - return (char)0; - } - } - } - - /// Add quotes to the item. - /// the array item - /// the open quote character - /// the closing quote character - /// flag if commas are allowed - /// Returns the value in quotes. - private static string ApplyQuotes(string item, char openQuote, char closeQuote, bool allowCommas) - { - if (item == null) - { - item = string.Empty; - } - bool prevSpace = false; - int charOffset; - int charKind; - // See if there are any separators in the value. Stop at the first - // occurrance. This is a bit - // tricky in order to make typical typing work conveniently. The purpose - // of applying quotes - // is to preserve the values when splitting them back apart. That is - // CatenateContainerItems - // and SeparateContainerItems must round trip properly. For the most - // part we only look for - // separators here. Internal quotes, as in -- Irving "Bud" Jones -- - // won't cause problems in - // the separation. An initial quote will though, it will make the value - // look quoted. - int i; - for (i = 0; i < item.Length; i++) - { - char ch = item[i]; - charKind = ClassifyCharacter(ch); - if (i == 0 && charKind == UckQuote) - { - break; - } - if (charKind == UckSpace) - { - // Multiple spaces are a separator. - if (prevSpace) - { - break; - } - prevSpace = true; - } - else - { - prevSpace = false; - if ((charKind == UckSemicolon || charKind == UckControl) || (charKind == UckComma && !allowCommas)) - { - break; - } - } - } - if (i < item.Length) - { - // Create a quoted copy, doubling any internal quotes that match the - // outer ones. Internal quotes did not stop the "needs quoting" - // search, but they do need - // doubling. So we have to rescan the front of the string for - // quotes. Handle the special - // case of U+301D being closed by either U+301E or U+301F. - StringBuilder newItem = new StringBuilder(item.Length + 2); - int splitPoint; - for (splitPoint = 0; splitPoint <= i; splitPoint++) - { - if (ClassifyCharacter(item[i]) == UckQuote) - { - break; - } - } - // Copy the leading "normal" portion. - newItem.Append(openQuote).Append(Runtime.Substring(item, 0, splitPoint)); - for (charOffset = splitPoint; charOffset < item.Length; charOffset++) - { - newItem.Append(item[charOffset]); - if (ClassifyCharacter(item[charOffset]) == UckQuote && IsSurroundingQuote(item[charOffset], openQuote, closeQuote)) - { - newItem.Append(item[charOffset]); - } - } - newItem.Append(closeQuote); - item = newItem.ToString(); - } - return item; - } - - /// a character - /// the opening quote char - /// the closing quote char - /// Return it the character is a surrounding quote. - private static bool IsSurroundingQuote(char ch, char openQuote, char closeQuote) - { - return ch == openQuote || IsClosingingQuote(ch, openQuote, closeQuote); - } - - /// a character - /// the opening quote char - /// the closing quote char - /// Returns true if the character is a closing quote. - private static bool IsClosingingQuote(char ch, char openQuote, char closeQuote) - { - return ch == closeQuote || (openQuote == unchecked((int)(0x301D)) && ch == unchecked((int)(0x301E)) || ch == unchecked((int)(0x301F))); - } - - /// - /// U+0022 ASCII space
- /// U+3000, ideographic space
- /// U+303F, ideographic half fill space
- /// U+2000..U+200B, en quad through zero width space - ///
- private const string Spaces = "\u0020\u3000\u303F"; - - /// - /// U+002C, ASCII comma
- /// U+FF0C, full width comma
- /// U+FF64, half width ideographic comma
- /// U+FE50, small comma
- /// U+FE51, small ideographic comma
- /// U+3001, ideographic comma
- /// U+060C, Arabic comma
- /// U+055D, Armenian comma - ///
- private const string Commas = "\u002C\uFF0C\uFF64\uFE50\uFE51\u3001\u060C\u055D"; - - /// - /// U+003B, ASCII semicolon
- /// U+FF1B, full width semicolon
- /// U+FE54, small semicolon
- /// U+061B, Arabic semicolon
- /// U+037E, Greek "semicolon" (really a question mark) - ///
- private const string Semicola = "\u003B\uFF1B\uFE54\u061B\u037E"; - - /// - /// U+0022 ASCII quote
- /// The square brackets are not interpreted as quotes anymore (bug #2674672) - /// (ASCII '[' (0x5B) and ']' (0x5D) are used as quotes in Chinese and - /// Korean.)
- /// U+00AB and U+00BB, guillemet quotes
- /// U+3008..U+300F, various quotes.
- /// U+301D..U+301F, double prime quotes.
- /// U+2015, dash quote.
- /// U+2018..U+201F, various quotes.
- /// U+2039 and U+203A, guillemet quotes. - ///
- private const string Quotes = "\"\u00AB\u00BB\u301D\u301E\u301F\u2015\u2039\u203A"; - - /// - /// U+0000..U+001F ASCII controls
- /// U+2028, line separator.
- /// U+2029, paragraph separator. - ///
- private const string Controls = "\u2028\u2029"; - // "\"\u005B\u005D\u00AB\u00BB\u301D\u301E\u301F\u2015\u2039\u203A"; - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Diagnostics; +using System.Text; +using Com.Adobe.Xmp.Impl.Xpath; +using Com.Adobe.Xmp.Options; +using Com.Adobe.Xmp.Properties; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl +{ + /// 11.08.2006 + public class XMPUtilsImpl : XMPConst + { + private const int UckNormal = 0; + + private const int UckSpace = 1; + + private const int UckComma = 2; + + private const int UckSemicolon = 3; + + private const int UckQuote = 4; + + private const int UckControl = 5; + + /// Private constructor, as + private XMPUtilsImpl() + { + } + + // EMPTY + /// + /// The XMP object containing the array to be catenated. + /// + /// The schema namespace URI for the array. Must not be null or + /// the empty string. + /// + /// + /// The name of the array. May be a general path expression, must + /// not be null or the empty string. Each item in the array must + /// be a simple string value. + /// + /// + /// The string to be used to separate the items in the catenated + /// string. Defaults to "; ", ASCII semicolon and space + /// (U+003B, U+0020). + /// + /// + /// The characters to be used as quotes around array items that + /// contain a separator. Defaults to '"' + /// + /// Option flag to control the catenation. + /// Returns the string containing the catenated array items. + /// Forwards the Exceptions from the metadata processing + public static string CatenateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string separator, string quotes, bool allowCommas) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + ParameterAsserts.AssertImplementation(xmp); + if (separator == null || separator.Length == 0) + { + separator = "; "; + } + if (quotes == null || quotes.Length == 0) + { + quotes = "\""; + } + XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; + XMPNode arrayNode = null; + XMPNode currItem = null; + // Return an empty result if the array does not exist, + // hurl if it isn't the right form. + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); + arrayNode = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), arrayPath, false, null); + if (arrayNode == null) + { + return string.Empty; + } + else + { + if (!arrayNode.GetOptions().IsArray() || arrayNode.GetOptions().IsArrayAlternate()) + { + throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badparam); + } + } + // Make sure the separator is OK. + CheckSeparator(separator); + // Make sure the open and close quotes are a legitimate pair. + char openQuote = quotes[0]; + char closeQuote = CheckQuotes(quotes, openQuote); + // Build the result, quoting the array items, adding separators. + // Hurl if any item isn't simple. + StringBuilder catinatedString = new StringBuilder(); + for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) + { + currItem = (XMPNode)it.Next(); + if (currItem.GetOptions().IsCompositeProperty()) + { + throw new XMPException("Array items must be simple", XMPErrorConstants.Badparam); + } + string str = ApplyQuotes(currItem.GetValue(), openQuote, closeQuote, allowCommas); + catinatedString.Append(str); + if (it.HasNext()) + { + catinatedString.Append(separator); + } + } + return catinatedString.ToString(); + } + + /// + /// see + /// + /// + /// The XMP object containing the array to be updated. + /// + /// The schema namespace URI for the array. Must not be null or + /// the empty string. + /// + /// + /// The name of the array. May be a general path expression, must + /// not be null or the empty string. Each item in the array must + /// be a simple string value. + /// + /// The string to be separated into the array items. + /// Option flags to control the separation. + /// Flag if commas shall be preserved + /// Forwards the Exceptions from the metadata processing + public static void SeparateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) + { + ParameterAsserts.AssertSchemaNS(schemaNS); + ParameterAsserts.AssertArrayName(arrayName); + if (catedStr == null) + { + throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); + } + ParameterAsserts.AssertImplementation(xmp); + XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; + // Keep a zero value, has special meaning below. + XMPNode arrayNode = SeparateFindCreateArray(schemaNS, arrayName, arrayOptions, xmpImpl); + // Extract the item values one at a time, until the whole input string is done. + string itemValue; + int itemStart; + int itemEnd; + int nextKind = UckNormal; + int charKind = UckNormal; + char ch = (char)0; + char nextChar = (char)0; + itemEnd = 0; + int endPos = catedStr.Length; + while (itemEnd < endPos) + { + // Skip any leading spaces and separation characters. Always skip commas here. + // They can be kept when within a value, but not when alone between values. + for (itemStart = itemEnd; itemStart < endPos; itemStart++) + { + ch = catedStr[itemStart]; + charKind = ClassifyCharacter(ch); + if (charKind == UckNormal || charKind == UckQuote) + { + break; + } + } + if (itemStart >= endPos) + { + break; + } + if (charKind != UckQuote) + { + // This is not a quoted value. Scan for the end, create an array + // item from the substring. + for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) + { + ch = catedStr[itemEnd]; + charKind = ClassifyCharacter(ch); + if (charKind == UckNormal || charKind == UckQuote || (charKind == UckComma && preserveCommas)) + { + continue; + } + else + { + if (charKind != UckSpace) + { + break; + } + else + { + if ((itemEnd + 1) < endPos) + { + ch = catedStr[itemEnd + 1]; + nextKind = ClassifyCharacter(ch); + if (nextKind == UckNormal || nextKind == UckQuote || (nextKind == UckComma && preserveCommas)) + { + continue; + } + } + } + } + // Anything left? + break; + } + // Have multiple spaces, or a space followed by a + // separator. + itemValue = Runtime.Substring(catedStr, itemStart, itemEnd); + } + else + { + // Accumulate quoted values into a local string, undoubling + // internal quotes that + // match the surrounding quotes. Do not undouble "unmatching" + // quotes. + char openQuote = ch; + char closeQuote = GetClosingQuote(openQuote); + itemStart++; + // Skip the opening quote; + itemValue = string.Empty; + for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) + { + ch = catedStr[itemEnd]; + charKind = ClassifyCharacter(ch); + if (charKind != UckQuote || !IsSurroundingQuote(ch, openQuote, closeQuote)) + { + // This is not a matching quote, just append it to the + // item value. + itemValue += ch; + } + else + { + // This is a "matching" quote. Is it doubled, or the + // final closing quote? + // Tolerate various edge cases like undoubled opening + // (non-closing) quotes, + // or end of input. + if ((itemEnd + 1) < endPos) + { + nextChar = catedStr[itemEnd + 1]; + nextKind = ClassifyCharacter(nextChar); + } + else + { + nextKind = UckSemicolon; + nextChar = (char)0x3B; + } + if (ch == nextChar) + { + // This is doubled, copy it and skip the double. + itemValue += ch; + // Loop will add in charSize. + itemEnd++; + } + else + { + if (!IsClosingingQuote(ch, openQuote, closeQuote)) + { + // This is an undoubled, non-closing quote, copy it. + itemValue += ch; + } + else + { + // This is an undoubled closing quote, skip it and + // exit the loop. + itemEnd++; + break; + } + } + } + } + } + // Add the separated item to the array. + // Keep a matching old value in case it had separators. + int foundIndex = -1; + for (int oldChild = 1; oldChild <= arrayNode.GetChildrenLength(); oldChild++) + { + if (itemValue.Equals(arrayNode.GetChild(oldChild).GetValue())) + { + foundIndex = oldChild; + break; + } + } + XMPNode newItem = null; + if (foundIndex < 0) + { + newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); + arrayNode.AddChild(newItem); + } + } + } + + /// Utility to find or create the array used by separateArrayItems(). + /// a the namespace fo the array + /// the name of the array + /// the options for the array if newly created + /// the xmp object + /// Returns the array node. + /// Forwards exceptions + private static XMPNode SeparateFindCreateArray(string schemaNS, string arrayName, PropertyOptions arrayOptions, XMPMetaImpl xmp) + { + arrayOptions = XMPNodeUtils.VerifySetOptions(arrayOptions, null); + if (!arrayOptions.IsOnlyArrayOptions()) + { + throw new XMPException("Options can only provide array form", XMPErrorConstants.Badoptions); + } + // Find the array node, make sure it is OK. Move the current children + // aside, to be readded later if kept. + XMPPath arrayPath = XMPPathParser.ExpandXPath(schemaNS, arrayName); + XMPNode arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, false, null); + if (arrayNode != null) + { + // The array exists, make sure the form is compatible. Zero + // arrayForm means take what exists. + PropertyOptions arrayForm = arrayNode.GetOptions(); + if (!arrayForm.IsArray() || arrayForm.IsArrayAlternate()) + { + throw new XMPException("Named property must be non-alternate array", XMPErrorConstants.Badxpath); + } + if (arrayOptions.EqualArrayTypes(arrayForm)) + { + throw new XMPException("Mismatch of specified and existing array form", XMPErrorConstants.Badxpath); + } + } + else + { + // *** Right error? + // The array does not exist, try to create it. + // don't modify the options handed into the method + arrayNode = XMPNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions.SetArray(true)); + if (arrayNode == null) + { + throw new XMPException("Failed to create named array", XMPErrorConstants.Badxpath); + } + } + return arrayNode; + } + + /// + /// The XMP object containing the properties to be removed. + /// + /// Optional schema namespace URI for the properties to be + /// removed. + /// + /// Optional path expression for the property to be removed. + /// + /// Option flag to control the deletion: do internal properties in + /// addition to external properties. + /// + /// + /// Option flag to control the deletion: Include aliases in the + /// "named schema" case above. + /// + /// If metadata processing fails + public static void RemoveProperties(XMPMeta xmp, string schemaNS, string propName, bool doAllProperties, bool includeAliases) + { + ParameterAsserts.AssertImplementation(xmp); + XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; + if (propName != null && propName.Length > 0) + { + // Remove just the one indicated property. This might be an alias, + // the named schema might not actually exist. So don't lookup the + // schema node. + if (schemaNS == null || schemaNS.Length == 0) + { + throw new XMPException("Property name requires schema namespace", XMPErrorConstants.Badparam); + } + XMPPath expPath = XMPPathParser.ExpandXPath(schemaNS, propName); + XMPNode propNode = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), expPath, false, null); + if (propNode != null) + { + if (doAllProperties || !Utils.IsInternalProperty(expPath.GetSegment(XMPPath.StepSchema).GetName(), expPath.GetSegment(XMPPath.StepRootProp).GetName())) + { + XMPNode parent = propNode.GetParent(); + parent.RemoveChild(propNode); + if (parent.GetOptions().IsSchemaNode() && !parent.HasChildren()) + { + // remove empty schema node + parent.GetParent().RemoveChild(parent); + } + } + } + } + else + { + if (schemaNS != null && schemaNS.Length > 0) + { + // Remove all properties from the named schema. Optionally include + // aliases, in which case + // there might not be an actual schema node. + // XMP_NodePtrPos schemaPos; + XMPNode schemaNode = XMPNodeUtils.FindSchemaNode(xmpImpl.GetRoot(), schemaNS, false); + if (schemaNode != null) + { + if (RemoveSchemaChildren(schemaNode, doAllProperties)) + { + xmpImpl.GetRoot().RemoveChild(schemaNode); + } + } + if (includeAliases) + { + // We're removing the aliases also. Look them up by their + // namespace prefix. + // But that takes more code and the extra speed isn't worth it. + // Lookup the XMP node + // from the alias, to make sure the actual exists. + XMPAliasInfo[] aliases = XMPMetaFactory.GetSchemaRegistry().FindAliases(schemaNS); + for (int i = 0; i < aliases.Length; i++) + { + XMPAliasInfo info = aliases[i]; + XMPPath path = XMPPathParser.ExpandXPath(info.GetNamespace(), info.GetPropName()); + XMPNode actualProp = XMPNodeUtils.FindNode(xmpImpl.GetRoot(), path, false, null); + if (actualProp != null) + { + XMPNode parent = actualProp.GetParent(); + parent.RemoveChild(actualProp); + } + } + } + } + else + { + // Remove all appropriate properties from all schema. In this case + // we don't have to be + // concerned with aliases, they are handled implicitly from the + // actual properties. + for (Iterator it = xmpImpl.GetRoot().IterateChildren(); it.HasNext(); ) + { + XMPNode schema = (XMPNode)it.Next(); + if (RemoveSchemaChildren(schema, doAllProperties)) + { + it.Remove(); + } + } + } + } + } + + /// + /// The source XMP object. + /// The destination XMP object. + /// Do internal properties in addition to external properties. + /// Replace the values of existing properties. + /// Delete destination values if source property is empty. + /// Forwards the Exceptions from the metadata processing + public static void AppendProperties(XMPMeta source, XMPMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) + { + ParameterAsserts.AssertImplementation(source); + ParameterAsserts.AssertImplementation(destination); + XMPMetaImpl src = (XMPMetaImpl)source; + XMPMetaImpl dest = (XMPMetaImpl)destination; + for (Iterator it = src.GetRoot().IterateChildren(); it.HasNext(); ) + { + XMPNode sourceSchema = (XMPNode)it.Next(); + // Make sure we have a destination schema node + XMPNode destSchema = XMPNodeUtils.FindSchemaNode(dest.GetRoot(), sourceSchema.GetName(), false); + bool createdSchema = false; + if (destSchema == null) + { + destSchema = new XMPNode(sourceSchema.GetName(), sourceSchema.GetValue(), new PropertyOptions().SetSchemaNode(true)); + dest.GetRoot().AddChild(destSchema); + createdSchema = true; + } + // Process the source schema's children. + for (Iterator ic = sourceSchema.IterateChildren(); ic.HasNext(); ) + { + XMPNode sourceProp = (XMPNode)ic.Next(); + if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.GetName(), sourceProp.GetName())) + { + AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); + } + } + if (!destSchema.HasChildren() && (createdSchema || deleteEmptyValues)) + { + // Don't create an empty schema / remove empty schema. + dest.GetRoot().RemoveChild(destSchema); + } + } + } + + /// + /// Remove all schema children according to the flag + /// doAllProperties. + /// + /// + /// Remove all schema children according to the flag + /// doAllProperties. Empty schemas are automatically remove + /// by XMPNode + /// + /// a schema node + /// flag if all properties or only externals shall be removed. + /// Returns true if the schema is empty after the operation. + private static bool RemoveSchemaChildren(XMPNode schemaNode, bool doAllProperties) + { + for (Iterator it = schemaNode.IterateChildren(); it.HasNext(); ) + { + XMPNode currProp = (XMPNode)it.Next(); + if (doAllProperties || !Utils.IsInternalProperty(schemaNode.GetName(), currProp.GetName())) + { + it.Remove(); + } + } + return !schemaNode.HasChildren(); + } + + /// + /// The destination XMP object. + /// the source node + /// the parent of the destination node + /// Replace the values of existing properties. + /// + /// flag if properties with empty values should be deleted + /// in the destination object. + /// + /// + private static void AppendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent, bool replaceOldValues, bool deleteEmptyValues) + { + XMPNode destNode = XMPNodeUtils.FindChildNode(destParent, sourceNode.GetName(), false); + bool valueIsEmpty = false; + if (deleteEmptyValues) + { + valueIsEmpty = sourceNode.GetOptions().IsSimple() ? sourceNode.GetValue() == null || sourceNode.GetValue().Length == 0 : !sourceNode.HasChildren(); + } + if (deleteEmptyValues && valueIsEmpty) + { + if (destNode != null) + { + destParent.RemoveChild(destNode); + } + } + else + { + if (destNode == null) + { + // The one easy case, the destination does not exist. + destParent.AddChild((XMPNode)sourceNode.Clone()); + } + else + { + if (replaceOldValues) + { + // The destination exists and should be replaced. + destXMP.SetNode(destNode, sourceNode.GetValue(), sourceNode.GetOptions(), true); + destParent.RemoveChild(destNode); + destNode = (XMPNode)sourceNode.Clone(); + destParent.AddChild(destNode); + } + else + { + // The destination exists and is not totally replaced. Structs and + // arrays are merged. + PropertyOptions sourceForm = sourceNode.GetOptions(); + PropertyOptions destForm = destNode.GetOptions(); + if (sourceForm != destForm) + { + return; + } + if (sourceForm.IsStruct()) + { + // To merge a struct process the fields recursively. E.g. add simple missing fields. + // The recursive call to AppendSubtree will handle deletion for fields with empty + // values. + for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) + { + XMPNode sourceField = (XMPNode)it.Next(); + AppendSubtree(destXMP, sourceField, destNode, replaceOldValues, deleteEmptyValues); + if (deleteEmptyValues && !destNode.HasChildren()) + { + destParent.RemoveChild(destNode); + } + } + } + else + { + if (sourceForm.IsArrayAltText()) + { + // Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. + // Make a special check for deletion of empty values. Meaningful in AltText arrays + // because the "xml:lang" qualifier provides unambiguous source/dest correspondence. + for (Iterator it = sourceNode.IterateChildren(); it.HasNext(); ) + { + XMPNode sourceItem = (XMPNode)it.Next(); + if (!sourceItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(sourceItem.GetQualifier(1).GetName())) + { + continue; + } + int destIndex = XMPNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).GetValue()); + if (deleteEmptyValues && (sourceItem.GetValue() == null || sourceItem.GetValue().Length == 0)) + { + if (destIndex != -1) + { + destNode.RemoveChild(destIndex); + if (!destNode.HasChildren()) + { + destParent.RemoveChild(destNode); + } + } + } + else + { + if (destIndex == -1) + { + // Not replacing, keep the existing item. + if (!XMPConstConstants.XDefault.Equals(sourceItem.GetQualifier(1).GetValue()) || !destNode.HasChildren()) + { + sourceItem.CloneSubtree(destNode); + } + else + { + XMPNode destItem = new XMPNode(sourceItem.GetName(), sourceItem.GetValue(), sourceItem.GetOptions()); + sourceItem.CloneSubtree(destItem); + destNode.AddChild(1, destItem); + } + } + } + } + } + else + { + if (sourceForm.IsArray()) + { + // Merge other arrays by item values. Don't worry about order or duplicates. Source + // items with empty values do not cause deletion, that conflicts horribly with + // merging. + for (Iterator @is = sourceNode.IterateChildren(); @is.HasNext(); ) + { + XMPNode sourceItem = (XMPNode)@is.Next(); + bool match = false; + for (Iterator id = destNode.IterateChildren(); id.HasNext(); ) + { + XMPNode destItem = (XMPNode)id.Next(); + if (ItemValuesMatch(sourceItem, destItem)) + { + match = true; + } + } + if (!match) + { + destNode = (XMPNode)sourceItem.Clone(); + destParent.AddChild(destNode); + } + } + } + } + } + } + } + } + } + + /// Compares two nodes including its children and qualifier. + /// an XMPNode + /// an XMPNode + /// Returns true if the nodes are equal, false otherwise. + /// Forwards exceptions to the calling method. + private static bool ItemValuesMatch(XMPNode leftNode, XMPNode rightNode) + { + PropertyOptions leftForm = leftNode.GetOptions(); + PropertyOptions rightForm = rightNode.GetOptions(); + if (leftForm.Equals(rightForm)) + { + return false; + } + if (leftForm.GetOptions() == 0) + { + // Simple nodes, check the values and xml:lang qualifiers. + if (!leftNode.GetValue().Equals(rightNode.GetValue())) + { + return false; + } + if (leftNode.GetOptions().GetHasLanguage() != rightNode.GetOptions().GetHasLanguage()) + { + return false; + } + if (leftNode.GetOptions().GetHasLanguage() && !leftNode.GetQualifier(1).GetValue().Equals(rightNode.GetQualifier(1).GetValue())) + { + return false; + } + } + else + { + if (leftForm.IsStruct()) + { + // Struct nodes, see if all fields match, ignoring order. + if (leftNode.GetChildrenLength() != rightNode.GetChildrenLength()) + { + return false; + } + for (Iterator it = leftNode.IterateChildren(); it.HasNext(); ) + { + XMPNode leftField = (XMPNode)it.Next(); + XMPNode rightField = XMPNodeUtils.FindChildNode(rightNode, leftField.GetName(), false); + if (rightField == null || !ItemValuesMatch(leftField, rightField)) + { + return false; + } + } + } + else + { + // Array nodes, see if the "leftNode" values are present in the + // "rightNode", ignoring order, duplicates, + // and extra values in the rightNode-> The rightNode is the + // destination for AppendProperties. + Debug.Assert(leftForm.IsArray()); + for (Iterator il = leftNode.IterateChildren(); il.HasNext(); ) + { + XMPNode leftItem = (XMPNode)il.Next(); + bool match = false; + for (Iterator ir = rightNode.IterateChildren(); ir.HasNext(); ) + { + XMPNode rightItem = (XMPNode)ir.Next(); + if (ItemValuesMatch(leftItem, rightItem)) + { + match = true; + break; + } + } + if (!match) + { + return false; + } + } + } + } + return true; + } + + // All of the checks passed. + /// Make sure the separator is OK. + /// + /// Make sure the separator is OK. It must be one semicolon surrounded by + /// zero or more spaces. Any of the recognized semicolons or spaces are + /// allowed. + /// + /// + /// + private static void CheckSeparator(string separator) + { + bool haveSemicolon = false; + for (int i = 0; i < separator.Length; i++) + { + int charKind = ClassifyCharacter(separator[i]); + if (charKind == UckSemicolon) + { + if (haveSemicolon) + { + throw new XMPException("Separator can have only one semicolon", XMPErrorConstants.Badparam); + } + haveSemicolon = true; + } + else + { + if (charKind != UckSpace) + { + throw new XMPException("Separator can have only spaces and one semicolon", XMPErrorConstants.Badparam); + } + } + } + if (!haveSemicolon) + { + throw new XMPException("Separator must have one semicolon", XMPErrorConstants.Badparam); + } + } + + /// + /// Make sure the open and close quotes are a legitimate pair and return the + /// correct closing quote or an exception. + /// + /// opened and closing quote in a string + /// the open quote + /// Returns a corresponding closing quote. + /// + private static char CheckQuotes(string quotes, char openQuote) + { + char closeQuote; + int charKind = ClassifyCharacter(openQuote); + if (charKind != UckQuote) + { + throw new XMPException("Invalid quoting character", XMPErrorConstants.Badparam); + } + if (quotes.Length == 1) + { + closeQuote = openQuote; + } + else + { + closeQuote = quotes[1]; + charKind = ClassifyCharacter(closeQuote); + if (charKind != UckQuote) + { + throw new XMPException("Invalid quoting character", XMPErrorConstants.Badparam); + } + } + if (closeQuote != GetClosingQuote(openQuote)) + { + throw new XMPException("Mismatched quote pair", XMPErrorConstants.Badparam); + } + return closeQuote; + } + + /// + /// Classifies the character into normal chars, spaces, semicola, quotes, + /// control chars. + /// + /// a char + /// Return the character kind. + private static int ClassifyCharacter(char ch) + { + if (Spaces.IndexOf(ch) >= 0 || (unchecked((int)(0x2000)) <= ch && ch <= unchecked((int)(0x200B)))) + { + return UckSpace; + } + else + { + if (Commas.IndexOf(ch) >= 0) + { + return UckComma; + } + else + { + if (Semicola.IndexOf(ch) >= 0) + { + return UckSemicolon; + } + else + { + if (Quotes.IndexOf(ch) >= 0 || (unchecked((int)(0x3008)) <= ch && ch <= unchecked((int)(0x300F))) || (unchecked((int)(0x2018)) <= ch && ch <= unchecked((int)(0x201F)))) + { + return UckQuote; + } + else + { + if (ch < unchecked((int)(0x0020)) || Controls.IndexOf(ch) >= 0) + { + return UckControl; + } + else + { + // Assume typical case. + return UckNormal; + } + } + } + } + } + } + + /// the open quote char + /// Returns the matching closing quote for an open quote. + private static char GetClosingQuote(char openQuote) + { + switch (openQuote) + { + case (char)0x0022: + { + return (char)0x0022; + } + + case (char)0x00AB: + { + // ! U+0022 is both opening and closing. + // Not interpreted as brackets anymore + // case 0x005B: + // return 0x005D; + return (char)0x00BB; + } + + case (char)0x00BB: + { + // ! U+00AB and U+00BB are reversible. + return (char)0x00AB; + } + + case (char)0x2015: + { + return (char)0x2015; + } + + case (char)0x2018: + { + // ! U+2015 is both opening and closing. + return (char)0x2019; + } + + case (char)0x201A: + { + return (char)0x201B; + } + + case (char)0x201C: + { + return (char)0x201D; + } + + case (char)0x201E: + { + return (char)0x201F; + } + + case (char)0x2039: + { + return (char)0x203A; + } + + case (char)0x203A: + { + // ! U+2039 and U+203A are reversible. + return (char)0x2039; + } + + case (char)0x3008: + { + return (char)0x3009; + } + + case (char)0x300A: + { + return (char)0x300B; + } + + case (char)0x300C: + { + return (char)0x300D; + } + + case (char)0x300E: + { + return (char)0x300F; + } + + case (char)0x301D: + { + return (char)0x301F; + } + + default: + { + // ! U+301E also closes U+301D. + return (char)0; + } + } + } + + /// Add quotes to the item. + /// the array item + /// the open quote character + /// the closing quote character + /// flag if commas are allowed + /// Returns the value in quotes. + private static string ApplyQuotes(string item, char openQuote, char closeQuote, bool allowCommas) + { + if (item == null) + { + item = string.Empty; + } + bool prevSpace = false; + int charOffset; + int charKind; + // See if there are any separators in the value. Stop at the first + // occurrance. This is a bit + // tricky in order to make typical typing work conveniently. The purpose + // of applying quotes + // is to preserve the values when splitting them back apart. That is + // CatenateContainerItems + // and SeparateContainerItems must round trip properly. For the most + // part we only look for + // separators here. Internal quotes, as in -- Irving "Bud" Jones -- + // won't cause problems in + // the separation. An initial quote will though, it will make the value + // look quoted. + int i; + for (i = 0; i < item.Length; i++) + { + char ch = item[i]; + charKind = ClassifyCharacter(ch); + if (i == 0 && charKind == UckQuote) + { + break; + } + if (charKind == UckSpace) + { + // Multiple spaces are a separator. + if (prevSpace) + { + break; + } + prevSpace = true; + } + else + { + prevSpace = false; + if ((charKind == UckSemicolon || charKind == UckControl) || (charKind == UckComma && !allowCommas)) + { + break; + } + } + } + if (i < item.Length) + { + // Create a quoted copy, doubling any internal quotes that match the + // outer ones. Internal quotes did not stop the "needs quoting" + // search, but they do need + // doubling. So we have to rescan the front of the string for + // quotes. Handle the special + // case of U+301D being closed by either U+301E or U+301F. + StringBuilder newItem = new StringBuilder(item.Length + 2); + int splitPoint; + for (splitPoint = 0; splitPoint <= i; splitPoint++) + { + if (ClassifyCharacter(item[i]) == UckQuote) + { + break; + } + } + // Copy the leading "normal" portion. + newItem.Append(openQuote).Append(Runtime.Substring(item, 0, splitPoint)); + for (charOffset = splitPoint; charOffset < item.Length; charOffset++) + { + newItem.Append(item[charOffset]); + if (ClassifyCharacter(item[charOffset]) == UckQuote && IsSurroundingQuote(item[charOffset], openQuote, closeQuote)) + { + newItem.Append(item[charOffset]); + } + } + newItem.Append(closeQuote); + item = newItem.ToString(); + } + return item; + } + + /// a character + /// the opening quote char + /// the closing quote char + /// Return it the character is a surrounding quote. + private static bool IsSurroundingQuote(char ch, char openQuote, char closeQuote) + { + return ch == openQuote || IsClosingingQuote(ch, openQuote, closeQuote); + } + + /// a character + /// the opening quote char + /// the closing quote char + /// Returns true if the character is a closing quote. + private static bool IsClosingingQuote(char ch, char openQuote, char closeQuote) + { + return ch == closeQuote || (openQuote == unchecked((int)(0x301D)) && ch == unchecked((int)(0x301E)) || ch == unchecked((int)(0x301F))); + } + + /// + /// U+0022 ASCII space
+ /// U+3000, ideographic space
+ /// U+303F, ideographic half fill space
+ /// U+2000..U+200B, en quad through zero width space + ///
+ private const string Spaces = "\u0020\u3000\u303F"; + + /// + /// U+002C, ASCII comma
+ /// U+FF0C, full width comma
+ /// U+FF64, half width ideographic comma
+ /// U+FE50, small comma
+ /// U+FE51, small ideographic comma
+ /// U+3001, ideographic comma
+ /// U+060C, Arabic comma
+ /// U+055D, Armenian comma + ///
+ private const string Commas = "\u002C\uFF0C\uFF64\uFE50\uFE51\u3001\u060C\u055D"; + + /// + /// U+003B, ASCII semicolon
+ /// U+FF1B, full width semicolon
+ /// U+FE54, small semicolon
+ /// U+061B, Arabic semicolon
+ /// U+037E, Greek "semicolon" (really a question mark) + ///
+ private const string Semicola = "\u003B\uFF1B\uFE54\u061B\u037E"; + + /// + /// U+0022 ASCII quote
+ /// The square brackets are not interpreted as quotes anymore (bug #2674672) + /// (ASCII '[' (0x5B) and ']' (0x5D) are used as quotes in Chinese and + /// Korean.)
+ /// U+00AB and U+00BB, guillemet quotes
+ /// U+3008..U+300F, various quotes.
+ /// U+301D..U+301F, double prime quotes.
+ /// U+2015, dash quote.
+ /// U+2018..U+201F, various quotes.
+ /// U+2039 and U+203A, guillemet quotes. + ///
+ private const string Quotes = "\"\u00AB\u00BB\u301D\u301E\u301F\u2015\u2039\u203A"; + + /// + /// U+0000..U+001F ASCII controls
+ /// U+2028, line separator.
+ /// U+2029, paragraph separator. + ///
+ private const string Controls = "\u2028\u2029"; + // "\"\u005B\u005D\u00AB\u00BB\u301D\u301E\u301F\u2015\u2039\u203A"; + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPath.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPath.cs index 862d96e71..2827fcfd1 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPath.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPath.cs @@ -1,92 +1,92 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Collections; -using System.Text; - -namespace Com.Adobe.Xmp.Impl.Xpath -{ - /// Representates an XMP XMPPath with segment accessor methods. - /// 28.02.2006 - public class XMPPath - { - /// Marks a struct field step , also for top level nodes (schema "fields"). - public const int StructFieldStep = unchecked((int)(0x01)); - - /// Marks a qualifier step. - /// - /// Marks a qualifier step. - /// Note: Order is significant to separate struct/qual from array kinds! - /// - public const int QualifierStep = unchecked((int)(0x02)); - - /// Marks an array index step - public const int ArrayIndexStep = unchecked((int)(0x03)); - - public const int ArrayLastStep = unchecked((int)(0x04)); - - public const int QualSelectorStep = unchecked((int)(0x05)); - - public const int FieldSelectorStep = unchecked((int)(0x06)); - - public const int SchemaNode = unchecked((int)(0x80000000)); - - public const int StepSchema = 0; - - public const int StepRootProp = 1; - - /// stores the segments of an XMPPath - private IList segments = new ArrayList(5); - - // Bits for XPathStepInfo options. - // - /// Append a path segment - /// the segment to add - public virtual void Add(XMPPathSegment segment) - { - segments.Add(segment); - } - - /// the index of the segment to return - /// Returns a path segment. - public virtual XMPPathSegment GetSegment(int index) - { - return (XMPPathSegment)segments[index]; - } - - /// Returns the size of the xmp path. - public virtual int Size() - { - return segments.Count; - } - - /// Serializes the normalized XMP-path. - /// - public override string ToString() - { - StringBuilder result = new StringBuilder(); - int index = 1; - while (index < Size()) - { - result.Append(GetSegment(index)); - if (index < Size() - 1) - { - int kind = GetSegment(index + 1).GetKind(); - if (kind == StructFieldStep || kind == QualifierStep) - { - // all but last and array indices - result.Append('/'); - } - } - index++; - } - return result.ToString(); - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using System.Collections; +using System.Text; + +namespace Com.Adobe.Xmp.Impl.Xpath +{ + /// Representates an XMP XMPPath with segment accessor methods. + /// 28.02.2006 + public class XMPPath + { + /// Marks a struct field step , also for top level nodes (schema "fields"). + public const int StructFieldStep = unchecked((int)(0x01)); + + /// Marks a qualifier step. + /// + /// Marks a qualifier step. + /// Note: Order is significant to separate struct/qual from array kinds! + /// + public const int QualifierStep = unchecked((int)(0x02)); + + /// Marks an array index step + public const int ArrayIndexStep = unchecked((int)(0x03)); + + public const int ArrayLastStep = unchecked((int)(0x04)); + + public const int QualSelectorStep = unchecked((int)(0x05)); + + public const int FieldSelectorStep = unchecked((int)(0x06)); + + public const int SchemaNode = unchecked((int)(0x80000000)); + + public const int StepSchema = 0; + + public const int StepRootProp = 1; + + /// stores the segments of an XMPPath + private IList segments = new ArrayList(5); + + // Bits for XPathStepInfo options. + // + /// Append a path segment + /// the segment to add + public virtual void Add(XMPPathSegment segment) + { + segments.Add(segment); + } + + /// the index of the segment to return + /// Returns a path segment. + public virtual XMPPathSegment GetSegment(int index) + { + return (XMPPathSegment)segments[index]; + } + + /// Returns the size of the xmp path. + public virtual int Size() + { + return segments.Count; + } + + /// Serializes the normalized XMP-path. + /// + public override string ToString() + { + StringBuilder result = new StringBuilder(); + int index = 1; + while (index < Size()) + { + result.Append(GetSegment(index)); + if (index < Size() - 1) + { + int kind = GetSegment(index + 1).GetKind(); + if (kind == StructFieldStep || kind == QualifierStep) + { + // all but last and array indices + result.Append('/'); + } + } + index++; + } + return result.ToString(); + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathParser.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathParser.cs index 5e5e05d31..877987278 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathParser.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathParser.cs @@ -1,451 +1,451 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using Com.Adobe.Xmp.Properties; -using Sharpen; - -namespace Com.Adobe.Xmp.Impl.Xpath -{ - /// Parser for XMP XPaths. - /// 01.03.2006 - public sealed class XMPPathParser - { - /// Private constructor - private XMPPathParser() - { - } - - // empty - /// - /// Split an XMPPath expression apart at the conceptual steps, adding the - /// root namespace prefix to the first property component. - /// - /// - /// Split an XMPPath expression apart at the conceptual steps, adding the - /// root namespace prefix to the first property component. The schema URI is - /// put in the first (0th) slot in the expanded XMPPath. Check if the top - /// level component is an alias, but don't resolve it. - ///

- /// In the most verbose case steps are separated by '/', and each step can be - /// of these forms: - ///

- ///
prefix:name - ///
A top level property or struct field. - ///
[index] - ///
An element of an array. - ///
[last()] - ///
The last element of an array. - ///
[fieldName="value"] - ///
An element in an array of structs, chosen by a field value. - ///
[@xml:lang="value"] - ///
An element in an alt-text array, chosen by the xml:lang qualifier. - ///
[?qualName="value"] - ///
An element in an array, chosen by a qualifier value. - ///
@xml:lang - ///
An xml:lang qualifier. - ///
?qualName - ///
A general qualifier. - ///
- ///

- /// The logic is complicated though by shorthand for arrays, the separating - /// '/' and leading '*' are optional. These are all equivalent: array/*[2] - /// array/[2] array*[2] array[2] All of these are broken into the 2 steps - /// "array" and "[2]". - ///

- /// The value portion in the array selector forms is a string quoted by ''' - /// or '"'. The value may contain any character including a doubled quoting - /// character. The value may be empty. - ///

- /// The syntax isn't checked, but an XML name begins with a letter or '_', - /// and contains letters, digits, '.', '-', '_', and a bunch of special - /// non-ASCII Unicode characters. An XML qualified name is a pair of names - /// separated by a colon. - /// - /// schema namespace - /// property name - /// Returns the expandet XMPPath. - /// Thrown if the format is not correct somehow. - public static XMPPath ExpandXPath(string schemaNS, string path) - { - if (schemaNS == null || path == null) - { - throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); - } - XMPPath expandedXPath = new XMPPath(); - PathPosition pos = new PathPosition(); - pos.path = path; - // Pull out the first component and do some special processing on it: add the schema - // namespace prefix and and see if it is an alias. The start must be a "qualName". - ParseRootNode(schemaNS, pos, expandedXPath); - // Now continue to process the rest of the XMPPath string. - while (pos.stepEnd < path.Length) - { - pos.stepBegin = pos.stepEnd; - SkipPathDelimiter(path, pos); - pos.stepEnd = pos.stepBegin; - XMPPathSegment segment; - if (path[pos.stepBegin] != '[') - { - // A struct field or qualifier. - segment = ParseStructSegment(pos); - } - else - { - // One of the array forms. - segment = ParseIndexSegment(pos); - } - if (segment.GetKind() == XMPPath.StructFieldStep) - { - if (segment.GetName()[0] == '@') - { - segment.SetName("?" + Runtime.Substring(segment.GetName(), 1)); - if (!"?xml:lang".Equals(segment.GetName())) - { - throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); - } - } - if (segment.GetName()[0] == '?') - { - pos.nameStart++; - segment.SetKind(XMPPath.QualifierStep); - } - VerifyQualName(Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); - } - else - { - if (segment.GetKind() == XMPPath.FieldSelectorStep) - { - if (segment.GetName()[1] == '@') - { - segment.SetName("[?" + Runtime.Substring(segment.GetName(), 2)); - if (!segment.GetName().StartsWith("[?xml:lang=")) - { - throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); - } - } - if (segment.GetName()[1] == '?') - { - pos.nameStart++; - segment.SetKind(XMPPath.QualSelectorStep); - VerifyQualName(Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); - } - } - } - expandedXPath.Add(segment); - } - return expandedXPath; - } - - /// - /// - /// - private static void SkipPathDelimiter(string path, PathPosition pos) - { - if (path[pos.stepBegin] == '/') - { - // skip slash - pos.stepBegin++; - // added for Java - if (pos.stepBegin >= path.Length) - { - throw new XMPException("Empty XMPPath segment", XMPErrorConstants.Badxpath); - } - } - if (path[pos.stepBegin] == '*') - { - // skip asterisk - pos.stepBegin++; - if (pos.stepBegin >= path.Length || path[pos.stepBegin] != '[') - { - throw new XMPException("Missing '[' after '*'", XMPErrorConstants.Badxpath); - } - } - } - - ///

Parses a struct segment - /// the current position in the path - /// Retusn the segment or an errror - /// If the sement is empty - private static XMPPathSegment ParseStructSegment(PathPosition pos) - { - pos.nameStart = pos.stepBegin; - while (pos.stepEnd < pos.path.Length && "/[*".IndexOf(pos.path[pos.stepEnd]) < 0) - { - pos.stepEnd++; - } - pos.nameEnd = pos.stepEnd; - if (pos.stepEnd == pos.stepBegin) - { - throw new XMPException("Empty XMPPath segment", XMPErrorConstants.Badxpath); - } - // ! Touch up later, also changing '@' to '?'. - XMPPathSegment segment = new XMPPathSegment(Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd), XMPPath.StructFieldStep); - return segment; - } - - /// Parses an array index segment. - /// the xmp path - /// Returns the segment or an error - /// thrown on xmp path errors - private static XMPPathSegment ParseIndexSegment(PathPosition pos) - { - XMPPathSegment segment; - pos.stepEnd++; - // Look at the character after the leading '['. - if ('0' <= pos.path[pos.stepEnd] && pos.path[pos.stepEnd] <= '9') - { - // A numeric (decimal integer) array index. - while (pos.stepEnd < pos.path.Length && '0' <= pos.path[pos.stepEnd] && pos.path[pos.stepEnd] <= '9') - { - pos.stepEnd++; - } - segment = new XMPPathSegment(null, XMPPath.ArrayIndexStep); - } - else - { - // Could be "[last()]" or one of the selector forms. Find the ']' or '='. - while (pos.stepEnd < pos.path.Length && pos.path[pos.stepEnd] != ']' && pos.path[pos.stepEnd] != '=') - { - pos.stepEnd++; - } - if (pos.stepEnd >= pos.path.Length) - { - throw new XMPException("Missing ']' or '=' for array index", XMPErrorConstants.Badxpath); - } - if (pos.path[pos.stepEnd] == ']') - { - if (!"[last()".Equals(Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd))) - { - throw new XMPException("Invalid non-numeric array index", XMPErrorConstants.Badxpath); - } - segment = new XMPPathSegment(null, XMPPath.ArrayLastStep); - } - else - { - pos.nameStart = pos.stepBegin + 1; - pos.nameEnd = pos.stepEnd; - pos.stepEnd++; - // Absorb the '=', remember the quote. - char quote = pos.path[pos.stepEnd]; - if (quote != '\'' && quote != '"') - { - throw new XMPException("Invalid quote in array selector", XMPErrorConstants.Badxpath); - } - pos.stepEnd++; - // Absorb the leading quote. - while (pos.stepEnd < pos.path.Length) - { - if (pos.path[pos.stepEnd] == quote) - { - // check for escaped quote - if (pos.stepEnd + 1 >= pos.path.Length || pos.path[pos.stepEnd + 1] != quote) - { - break; - } - pos.stepEnd++; - } - pos.stepEnd++; - } - if (pos.stepEnd >= pos.path.Length) - { - throw new XMPException("No terminating quote for array selector", XMPErrorConstants.Badxpath); - } - pos.stepEnd++; - // Absorb the trailing quote. - // ! Touch up later, also changing '@' to '?'. - segment = new XMPPathSegment(null, XMPPath.FieldSelectorStep); - } - } - if (pos.stepEnd >= pos.path.Length || pos.path[pos.stepEnd] != ']') - { - throw new XMPException("Missing ']' for array index", XMPErrorConstants.Badxpath); - } - pos.stepEnd++; - segment.SetName(Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd)); - return segment; - } - - /// - /// Parses the root node of an XMP Path, checks if namespace and prefix fit together - /// and resolve the property to the base property if it is an alias. - /// - /// the root namespace - /// the parsing position helper - /// the path to contribute to - /// If the path is not valid. - private static void ParseRootNode(string schemaNS, PathPosition pos, XMPPath expandedXPath) - { - while (pos.stepEnd < pos.path.Length && "/[*".IndexOf(pos.path[pos.stepEnd]) < 0) - { - pos.stepEnd++; - } - if (pos.stepEnd == pos.stepBegin) - { - throw new XMPException("Empty initial XMPPath step", XMPErrorConstants.Badxpath); - } - string rootProp = VerifyXPathRoot(schemaNS, Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd)); - XMPAliasInfo aliasInfo = XMPMetaFactory.GetSchemaRegistry().FindAlias(rootProp); - if (aliasInfo == null) - { - // add schema xpath step - expandedXPath.Add(new XMPPathSegment(schemaNS, XMPPath.SchemaNode)); - XMPPathSegment rootStep = new XMPPathSegment(rootProp, XMPPath.StructFieldStep); - expandedXPath.Add(rootStep); - } - else - { - // add schema xpath step and base step of alias - expandedXPath.Add(new XMPPathSegment(aliasInfo.GetNamespace(), XMPPath.SchemaNode)); - XMPPathSegment rootStep = new XMPPathSegment(VerifyXPathRoot(aliasInfo.GetNamespace(), aliasInfo.GetPropName()), XMPPath.StructFieldStep); - rootStep.SetAlias(true); - rootStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); - expandedXPath.Add(rootStep); - if (aliasInfo.GetAliasForm().IsArrayAltText()) - { - XMPPathSegment qualSelectorStep = new XMPPathSegment("[?xml:lang='x-default']", XMPPath.QualSelectorStep); - qualSelectorStep.SetAlias(true); - qualSelectorStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); - expandedXPath.Add(qualSelectorStep); - } - else - { - if (aliasInfo.GetAliasForm().IsArray()) - { - XMPPathSegment indexStep = new XMPPathSegment("[1]", XMPPath.ArrayIndexStep); - indexStep.SetAlias(true); - indexStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); - expandedXPath.Add(indexStep); - } - } - } - } - - /// - /// Verifies whether the qualifier name is not XML conformant or the - /// namespace prefix has not been registered. - /// - /// a qualifier name - /// If the name is not conformant - private static void VerifyQualName(string qualName) - { - int colonPos = qualName.IndexOf(':'); - if (colonPos > 0) - { - string prefix = Runtime.Substring(qualName, 0, colonPos); - if (Utils.IsXMLNameNS(prefix)) - { - string regURI = XMPMetaFactory.GetSchemaRegistry().GetNamespaceURI(prefix); - if (regURI != null) - { - return; - } - throw new XMPException("Unknown namespace prefix for qualified name", XMPErrorConstants.Badxpath); - } - } - throw new XMPException("Ill-formed qualified name", XMPErrorConstants.Badxpath); - } - - /// Verify if an XML name is conformant. - /// an XML name - /// When the name is not XML conformant - private static void VerifySimpleXMLName(string name) - { - if (!Utils.IsXMLName(name)) - { - throw new XMPException("Bad XML name", XMPErrorConstants.Badxpath); - } - } - - /// Set up the first 2 components of the expanded XMPPath. - /// - /// Set up the first 2 components of the expanded XMPPath. Normalizes the various cases of using - /// the full schema URI and/or a qualified root property name. Returns true for normal - /// processing. If allowUnknownSchemaNS is true and the schema namespace is not registered, false - /// is returned. If allowUnknownSchemaNS is false and the schema namespace is not registered, an - /// exception is thrown - ///

- /// (Should someday check the full syntax:) - /// - /// schema namespace - /// the root xpath segment - /// Returns root QName. - /// Thrown if the format is not correct somehow. - private static string VerifyXPathRoot(string schemaNS, string rootProp) - { - // Do some basic checks on the URI and name. Try to lookup the URI. See if the name is - // qualified. - if (schemaNS == null || schemaNS.Length == 0) - { - throw new XMPException("Schema namespace URI is required", XMPErrorConstants.Badschema); - } - if ((rootProp[0] == '?') || (rootProp[0] == '@')) - { - throw new XMPException("Top level name must not be a qualifier", XMPErrorConstants.Badxpath); - } - if (rootProp.IndexOf('/') >= 0 || rootProp.IndexOf('[') >= 0) - { - throw new XMPException("Top level name must be simple", XMPErrorConstants.Badxpath); - } - string prefix = XMPMetaFactory.GetSchemaRegistry().GetNamespacePrefix(schemaNS); - if (prefix == null) - { - throw new XMPException("Unregistered schema namespace URI", XMPErrorConstants.Badschema); - } - // Verify the various URI and prefix combinations. Initialize the - // expanded XMPPath. - int colonPos = rootProp.IndexOf(':'); - if (colonPos < 0) - { - // The propName is unqualified, use the schemaURI and associated - // prefix. - VerifySimpleXMLName(rootProp); - // Verify the part before any colon - return prefix + rootProp; - } - else - { - // The propName is qualified. Make sure the prefix is legit. Use the associated URI and - // qualified name. - // Verify the part before any colon - VerifySimpleXMLName(Runtime.Substring(rootProp, 0, colonPos)); - VerifySimpleXMLName(Runtime.Substring(rootProp, colonPos)); - prefix = Runtime.Substring(rootProp, 0, colonPos + 1); - string regPrefix = XMPMetaFactory.GetSchemaRegistry().GetNamespacePrefix(schemaNS); - if (regPrefix == null) - { - throw new XMPException("Unknown schema namespace prefix", XMPErrorConstants.Badschema); - } - if (!prefix.Equals(regPrefix)) - { - throw new XMPException("Schema namespace URI and prefix mismatch", XMPErrorConstants.Badschema); - } - return rootProp; - } - } - } - - ///

This objects contains all needed char positions to parse. - internal class PathPosition - { - /// the complete path - public string path = null; - - /// the start of a segment name - internal int nameStart = 0; - - /// the end of a segment name - internal int nameEnd = 0; - - /// the begin of a step - internal int stepBegin = 0; - - /// the end of a step - internal int stepEnd = 0; - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +using Com.Adobe.Xmp.Properties; +using Sharpen; + +namespace Com.Adobe.Xmp.Impl.Xpath +{ + /// Parser for XMP XPaths. + /// 01.03.2006 + public sealed class XMPPathParser + { + /// Private constructor + private XMPPathParser() + { + } + + // empty + /// + /// Split an XMPPath expression apart at the conceptual steps, adding the + /// root namespace prefix to the first property component. + /// + /// + /// Split an XMPPath expression apart at the conceptual steps, adding the + /// root namespace prefix to the first property component. The schema URI is + /// put in the first (0th) slot in the expanded XMPPath. Check if the top + /// level component is an alias, but don't resolve it. + ///

+ /// In the most verbose case steps are separated by '/', and each step can be + /// of these forms: + ///

+ ///
prefix:name + ///
A top level property or struct field. + ///
[index] + ///
An element of an array. + ///
[last()] + ///
The last element of an array. + ///
[fieldName="value"] + ///
An element in an array of structs, chosen by a field value. + ///
[@xml:lang="value"] + ///
An element in an alt-text array, chosen by the xml:lang qualifier. + ///
[?qualName="value"] + ///
An element in an array, chosen by a qualifier value. + ///
@xml:lang + ///
An xml:lang qualifier. + ///
?qualName + ///
A general qualifier. + ///
+ ///

+ /// The logic is complicated though by shorthand for arrays, the separating + /// '/' and leading '*' are optional. These are all equivalent: array/*[2] + /// array/[2] array*[2] array[2] All of these are broken into the 2 steps + /// "array" and "[2]". + ///

+ /// The value portion in the array selector forms is a string quoted by ''' + /// or '"'. The value may contain any character including a doubled quoting + /// character. The value may be empty. + ///

+ /// The syntax isn't checked, but an XML name begins with a letter or '_', + /// and contains letters, digits, '.', '-', '_', and a bunch of special + /// non-ASCII Unicode characters. An XML qualified name is a pair of names + /// separated by a colon. + /// + /// schema namespace + /// property name + /// Returns the expandet XMPPath. + /// Thrown if the format is not correct somehow. + public static XMPPath ExpandXPath(string schemaNS, string path) + { + if (schemaNS == null || path == null) + { + throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); + } + XMPPath expandedXPath = new XMPPath(); + PathPosition pos = new PathPosition(); + pos.path = path; + // Pull out the first component and do some special processing on it: add the schema + // namespace prefix and and see if it is an alias. The start must be a "qualName". + ParseRootNode(schemaNS, pos, expandedXPath); + // Now continue to process the rest of the XMPPath string. + while (pos.stepEnd < path.Length) + { + pos.stepBegin = pos.stepEnd; + SkipPathDelimiter(path, pos); + pos.stepEnd = pos.stepBegin; + XMPPathSegment segment; + if (path[pos.stepBegin] != '[') + { + // A struct field or qualifier. + segment = ParseStructSegment(pos); + } + else + { + // One of the array forms. + segment = ParseIndexSegment(pos); + } + if (segment.GetKind() == XMPPath.StructFieldStep) + { + if (segment.GetName()[0] == '@') + { + segment.SetName("?" + Runtime.Substring(segment.GetName(), 1)); + if (!"?xml:lang".Equals(segment.GetName())) + { + throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); + } + } + if (segment.GetName()[0] == '?') + { + pos.nameStart++; + segment.SetKind(XMPPath.QualifierStep); + } + VerifyQualName(Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); + } + else + { + if (segment.GetKind() == XMPPath.FieldSelectorStep) + { + if (segment.GetName()[1] == '@') + { + segment.SetName("[?" + Runtime.Substring(segment.GetName(), 2)); + if (!segment.GetName().StartsWith("[?xml:lang=")) + { + throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); + } + } + if (segment.GetName()[1] == '?') + { + pos.nameStart++; + segment.SetKind(XMPPath.QualSelectorStep); + VerifyQualName(Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); + } + } + } + expandedXPath.Add(segment); + } + return expandedXPath; + } + + /// + /// + /// + private static void SkipPathDelimiter(string path, PathPosition pos) + { + if (path[pos.stepBegin] == '/') + { + // skip slash + pos.stepBegin++; + // added for Java + if (pos.stepBegin >= path.Length) + { + throw new XMPException("Empty XMPPath segment", XMPErrorConstants.Badxpath); + } + } + if (path[pos.stepBegin] == '*') + { + // skip asterisk + pos.stepBegin++; + if (pos.stepBegin >= path.Length || path[pos.stepBegin] != '[') + { + throw new XMPException("Missing '[' after '*'", XMPErrorConstants.Badxpath); + } + } + } + + ///

Parses a struct segment + /// the current position in the path + /// Retusn the segment or an errror + /// If the sement is empty + private static XMPPathSegment ParseStructSegment(PathPosition pos) + { + pos.nameStart = pos.stepBegin; + while (pos.stepEnd < pos.path.Length && "/[*".IndexOf(pos.path[pos.stepEnd]) < 0) + { + pos.stepEnd++; + } + pos.nameEnd = pos.stepEnd; + if (pos.stepEnd == pos.stepBegin) + { + throw new XMPException("Empty XMPPath segment", XMPErrorConstants.Badxpath); + } + // ! Touch up later, also changing '@' to '?'. + XMPPathSegment segment = new XMPPathSegment(Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd), XMPPath.StructFieldStep); + return segment; + } + + /// Parses an array index segment. + /// the xmp path + /// Returns the segment or an error + /// thrown on xmp path errors + private static XMPPathSegment ParseIndexSegment(PathPosition pos) + { + XMPPathSegment segment; + pos.stepEnd++; + // Look at the character after the leading '['. + if ('0' <= pos.path[pos.stepEnd] && pos.path[pos.stepEnd] <= '9') + { + // A numeric (decimal integer) array index. + while (pos.stepEnd < pos.path.Length && '0' <= pos.path[pos.stepEnd] && pos.path[pos.stepEnd] <= '9') + { + pos.stepEnd++; + } + segment = new XMPPathSegment(null, XMPPath.ArrayIndexStep); + } + else + { + // Could be "[last()]" or one of the selector forms. Find the ']' or '='. + while (pos.stepEnd < pos.path.Length && pos.path[pos.stepEnd] != ']' && pos.path[pos.stepEnd] != '=') + { + pos.stepEnd++; + } + if (pos.stepEnd >= pos.path.Length) + { + throw new XMPException("Missing ']' or '=' for array index", XMPErrorConstants.Badxpath); + } + if (pos.path[pos.stepEnd] == ']') + { + if (!"[last()".Equals(Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd))) + { + throw new XMPException("Invalid non-numeric array index", XMPErrorConstants.Badxpath); + } + segment = new XMPPathSegment(null, XMPPath.ArrayLastStep); + } + else + { + pos.nameStart = pos.stepBegin + 1; + pos.nameEnd = pos.stepEnd; + pos.stepEnd++; + // Absorb the '=', remember the quote. + char quote = pos.path[pos.stepEnd]; + if (quote != '\'' && quote != '"') + { + throw new XMPException("Invalid quote in array selector", XMPErrorConstants.Badxpath); + } + pos.stepEnd++; + // Absorb the leading quote. + while (pos.stepEnd < pos.path.Length) + { + if (pos.path[pos.stepEnd] == quote) + { + // check for escaped quote + if (pos.stepEnd + 1 >= pos.path.Length || pos.path[pos.stepEnd + 1] != quote) + { + break; + } + pos.stepEnd++; + } + pos.stepEnd++; + } + if (pos.stepEnd >= pos.path.Length) + { + throw new XMPException("No terminating quote for array selector", XMPErrorConstants.Badxpath); + } + pos.stepEnd++; + // Absorb the trailing quote. + // ! Touch up later, also changing '@' to '?'. + segment = new XMPPathSegment(null, XMPPath.FieldSelectorStep); + } + } + if (pos.stepEnd >= pos.path.Length || pos.path[pos.stepEnd] != ']') + { + throw new XMPException("Missing ']' for array index", XMPErrorConstants.Badxpath); + } + pos.stepEnd++; + segment.SetName(Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd)); + return segment; + } + + /// + /// Parses the root node of an XMP Path, checks if namespace and prefix fit together + /// and resolve the property to the base property if it is an alias. + /// + /// the root namespace + /// the parsing position helper + /// the path to contribute to + /// If the path is not valid. + private static void ParseRootNode(string schemaNS, PathPosition pos, XMPPath expandedXPath) + { + while (pos.stepEnd < pos.path.Length && "/[*".IndexOf(pos.path[pos.stepEnd]) < 0) + { + pos.stepEnd++; + } + if (pos.stepEnd == pos.stepBegin) + { + throw new XMPException("Empty initial XMPPath step", XMPErrorConstants.Badxpath); + } + string rootProp = VerifyXPathRoot(schemaNS, Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd)); + XMPAliasInfo aliasInfo = XMPMetaFactory.GetSchemaRegistry().FindAlias(rootProp); + if (aliasInfo == null) + { + // add schema xpath step + expandedXPath.Add(new XMPPathSegment(schemaNS, XMPPath.SchemaNode)); + XMPPathSegment rootStep = new XMPPathSegment(rootProp, XMPPath.StructFieldStep); + expandedXPath.Add(rootStep); + } + else + { + // add schema xpath step and base step of alias + expandedXPath.Add(new XMPPathSegment(aliasInfo.GetNamespace(), XMPPath.SchemaNode)); + XMPPathSegment rootStep = new XMPPathSegment(VerifyXPathRoot(aliasInfo.GetNamespace(), aliasInfo.GetPropName()), XMPPath.StructFieldStep); + rootStep.SetAlias(true); + rootStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); + expandedXPath.Add(rootStep); + if (aliasInfo.GetAliasForm().IsArrayAltText()) + { + XMPPathSegment qualSelectorStep = new XMPPathSegment("[?xml:lang='x-default']", XMPPath.QualSelectorStep); + qualSelectorStep.SetAlias(true); + qualSelectorStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); + expandedXPath.Add(qualSelectorStep); + } + else + { + if (aliasInfo.GetAliasForm().IsArray()) + { + XMPPathSegment indexStep = new XMPPathSegment("[1]", XMPPath.ArrayIndexStep); + indexStep.SetAlias(true); + indexStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); + expandedXPath.Add(indexStep); + } + } + } + } + + /// + /// Verifies whether the qualifier name is not XML conformant or the + /// namespace prefix has not been registered. + /// + /// a qualifier name + /// If the name is not conformant + private static void VerifyQualName(string qualName) + { + int colonPos = qualName.IndexOf(':'); + if (colonPos > 0) + { + string prefix = Runtime.Substring(qualName, 0, colonPos); + if (Utils.IsXMLNameNS(prefix)) + { + string regURI = XMPMetaFactory.GetSchemaRegistry().GetNamespaceURI(prefix); + if (regURI != null) + { + return; + } + throw new XMPException("Unknown namespace prefix for qualified name", XMPErrorConstants.Badxpath); + } + } + throw new XMPException("Ill-formed qualified name", XMPErrorConstants.Badxpath); + } + + /// Verify if an XML name is conformant. + /// an XML name + /// When the name is not XML conformant + private static void VerifySimpleXMLName(string name) + { + if (!Utils.IsXMLName(name)) + { + throw new XMPException("Bad XML name", XMPErrorConstants.Badxpath); + } + } + + /// Set up the first 2 components of the expanded XMPPath. + /// + /// Set up the first 2 components of the expanded XMPPath. Normalizes the various cases of using + /// the full schema URI and/or a qualified root property name. Returns true for normal + /// processing. If allowUnknownSchemaNS is true and the schema namespace is not registered, false + /// is returned. If allowUnknownSchemaNS is false and the schema namespace is not registered, an + /// exception is thrown + ///

+ /// (Should someday check the full syntax:) + /// + /// schema namespace + /// the root xpath segment + /// Returns root QName. + /// Thrown if the format is not correct somehow. + private static string VerifyXPathRoot(string schemaNS, string rootProp) + { + // Do some basic checks on the URI and name. Try to lookup the URI. See if the name is + // qualified. + if (schemaNS == null || schemaNS.Length == 0) + { + throw new XMPException("Schema namespace URI is required", XMPErrorConstants.Badschema); + } + if ((rootProp[0] == '?') || (rootProp[0] == '@')) + { + throw new XMPException("Top level name must not be a qualifier", XMPErrorConstants.Badxpath); + } + if (rootProp.IndexOf('/') >= 0 || rootProp.IndexOf('[') >= 0) + { + throw new XMPException("Top level name must be simple", XMPErrorConstants.Badxpath); + } + string prefix = XMPMetaFactory.GetSchemaRegistry().GetNamespacePrefix(schemaNS); + if (prefix == null) + { + throw new XMPException("Unregistered schema namespace URI", XMPErrorConstants.Badschema); + } + // Verify the various URI and prefix combinations. Initialize the + // expanded XMPPath. + int colonPos = rootProp.IndexOf(':'); + if (colonPos < 0) + { + // The propName is unqualified, use the schemaURI and associated + // prefix. + VerifySimpleXMLName(rootProp); + // Verify the part before any colon + return prefix + rootProp; + } + else + { + // The propName is qualified. Make sure the prefix is legit. Use the associated URI and + // qualified name. + // Verify the part before any colon + VerifySimpleXMLName(Runtime.Substring(rootProp, 0, colonPos)); + VerifySimpleXMLName(Runtime.Substring(rootProp, colonPos)); + prefix = Runtime.Substring(rootProp, 0, colonPos + 1); + string regPrefix = XMPMetaFactory.GetSchemaRegistry().GetNamespacePrefix(schemaNS); + if (regPrefix == null) + { + throw new XMPException("Unknown schema namespace prefix", XMPErrorConstants.Badschema); + } + if (!prefix.Equals(regPrefix)) + { + throw new XMPException("Schema namespace URI and prefix mismatch", XMPErrorConstants.Badschema); + } + return rootProp; + } + } + } + + ///

This objects contains all needed char positions to parse. + internal class PathPosition + { + /// the complete path + public string path = null; + + /// the start of a segment name + internal int nameStart = 0; + + /// the end of a segment name + internal int nameEnd = 0; + + /// the begin of a step + internal int stepBegin = 0; + + /// the end of a step + internal int stepEnd = 0; + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathSegment.cs b/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathSegment.cs index 869c6c6dd..669f32dd3 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathSegment.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/impl/xpath/XMPPathSegment.cs @@ -1,119 +1,119 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -namespace Com.Adobe.Xmp.Impl.Xpath -{ - /// A segment of a parsed XMPPath. - /// 23.06.2006 - public class XMPPathSegment - { - /// name of the path segment - private string name; - - /// kind of the path segment - private int kind; - - /// flag if segment is an alias - private bool alias; - - /// alias form if applicable - private int aliasForm; - - /// Constructor with initial values. - /// the name of the segment - public XMPPathSegment(string name) - { - this.name = name; - } - - /// Constructor with initial values. - /// the name of the segment - /// the kind of the segment - public XMPPathSegment(string name, int kind) - { - this.name = name; - this.kind = kind; - } - - /// Returns the kind. - public virtual int GetKind() - { - return kind; - } - - /// The kind to set. - public virtual void SetKind(int kind) - { - this.kind = kind; - } - - /// Returns the name. - public virtual string GetName() - { - return name; - } - - /// The name to set. - public virtual void SetName(string name) - { - this.name = name; - } - - /// the flag to set - public virtual void SetAlias(bool alias) - { - this.alias = alias; - } - - /// Returns the alias. - public virtual bool IsAlias() - { - return alias; - } - - /// Returns the aliasForm if this segment has been created by an alias. - public virtual int GetAliasForm() - { - return aliasForm; - } - - /// the aliasForm to set - public virtual void SetAliasForm(int aliasForm) - { - this.aliasForm = aliasForm; - } - - /// - public override string ToString() - { - switch (kind) - { - case XMPPath.StructFieldStep: - case XMPPath.ArrayIndexStep: - case XMPPath.QualifierStep: - case XMPPath.ArrayLastStep: - { - return name; - } - - case XMPPath.QualSelectorStep: - case XMPPath.FieldSelectorStep: - { - return name; - } - - default: - { - // no defined step - return name; - } - } - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +namespace Com.Adobe.Xmp.Impl.Xpath +{ + /// A segment of a parsed XMPPath. + /// 23.06.2006 + public class XMPPathSegment + { + /// name of the path segment + private string name; + + /// kind of the path segment + private int kind; + + /// flag if segment is an alias + private bool alias; + + /// alias form if applicable + private int aliasForm; + + /// Constructor with initial values. + /// the name of the segment + public XMPPathSegment(string name) + { + this.name = name; + } + + /// Constructor with initial values. + /// the name of the segment + /// the kind of the segment + public XMPPathSegment(string name, int kind) + { + this.name = name; + this.kind = kind; + } + + /// Returns the kind. + public virtual int GetKind() + { + return kind; + } + + /// The kind to set. + public virtual void SetKind(int kind) + { + this.kind = kind; + } + + /// Returns the name. + public virtual string GetName() + { + return name; + } + + /// The name to set. + public virtual void SetName(string name) + { + this.name = name; + } + + /// the flag to set + public virtual void SetAlias(bool alias) + { + this.alias = alias; + } + + /// Returns the alias. + public virtual bool IsAlias() + { + return alias; + } + + /// Returns the aliasForm if this segment has been created by an alias. + public virtual int GetAliasForm() + { + return aliasForm; + } + + /// the aliasForm to set + public virtual void SetAliasForm(int aliasForm) + { + this.aliasForm = aliasForm; + } + + /// + public override string ToString() + { + switch (kind) + { + case XMPPath.StructFieldStep: + case XMPPath.ArrayIndexStep: + case XMPPath.QualifierStep: + case XMPPath.ArrayLastStep: + { + return name; + } + + case XMPPath.QualSelectorStep: + case XMPPath.FieldSelectorStep: + { + return name; + } + + default: + { + // no defined step + return name; + } + } + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/options/AliasOptions.cs b/Com.Adobe.Xmp/Com/adobe/xmp/options/AliasOptions.cs index 76e199c6e..abc1ff4aa 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/options/AliasOptions.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/options/AliasOptions.cs @@ -1,161 +1,161 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -namespace Com.Adobe.Xmp.Options -{ - /// Options for XMPSchemaRegistryImpl#registerAlias. - /// 20.02.2006 - public sealed class AliasOptions : Options - { - /// This is a direct mapping. - /// This is a direct mapping. The actual data type does not matter. - public const int PropDirect = 0; - - /// The actual is an unordered array, the alias is to the first element of the array. - public const int PropArray = PropertyOptions.Array; - - /// The actual is an ordered array, the alias is to the first element of the array. - public const int PropArrayOrdered = PropertyOptions.ArrayOrdered; - - /// The actual is an alternate array, the alias is to the first element of the array. - public const int PropArrayAlternate = PropertyOptions.ArrayAlternate; - - /// The actual is an alternate text array, the alias is to the 'x-default' element of the array. - public const int PropArrayAltText = PropertyOptions.ArrayAltText; - - /// - public AliasOptions() - { - } - - /// the options to init with - /// If options are not consistant - public AliasOptions(int options) - : base(options) - { - } - - // EMPTY - /// Returns if the alias is of the simple form. - public bool IsSimple() - { - return GetOptions() == PropDirect; - } - - /// Returns the option. - public bool IsArray() - { - return GetOption(PropArray); - } - - /// the value to set - /// Returns the instance to call more set-methods. - public AliasOptions SetArray(bool value) - { - SetOption(PropArray, value); - return this; - } - - /// Returns the option. - public bool IsArrayOrdered() - { - return GetOption(PropArrayOrdered); - } - - /// the value to set - /// Returns the instance to call more set-methods. - public AliasOptions SetArrayOrdered(bool value) - { - SetOption(PropArray | PropArrayOrdered, value); - return this; - } - - /// Returns the option. - public bool IsArrayAlternate() - { - return GetOption(PropArrayAlternate); - } - - /// the value to set - /// Returns the instance to call more set-methods. - public AliasOptions SetArrayAlternate(bool value) - { - SetOption(PropArray | PropArrayOrdered | PropArrayAlternate, value); - return this; - } - - /// Returns the option. - public bool IsArrayAltText() - { - return GetOption(PropArrayAltText); - } - - /// the value to set - /// Returns the instance to call more set-methods. - public AliasOptions SetArrayAltText(bool value) - { - SetOption(PropArray | PropArrayOrdered | PropArrayAlternate | PropArrayAltText, value); - return this; - } - - /// - /// returns a - /// - /// s object - /// - /// If the options are not consistant. - public PropertyOptions ToPropertyOptions() - { - return new PropertyOptions(GetOptions()); - } - - /// - protected internal override string DefineOptionName(int option) - { - switch (option) - { - case PropDirect: - { - return "PROP_DIRECT"; - } - - case PropArray: - { - return "ARRAY"; - } - - case PropArrayOrdered: - { - return "ARRAY_ORDERED"; - } - - case PropArrayAlternate: - { - return "ARRAY_ALTERNATE"; - } - - case PropArrayAltText: - { - return "ARRAY_ALT_TEXT"; - } - - default: - { - return null; - } - } - } - - /// - protected internal override int GetValidOptions() - { - return PropDirect | PropArray | PropArrayOrdered | PropArrayAlternate | PropArrayAltText; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +namespace Com.Adobe.Xmp.Options +{ + /// Options for XMPSchemaRegistryImpl#registerAlias. + /// 20.02.2006 + public sealed class AliasOptions : Options + { + /// This is a direct mapping. + /// This is a direct mapping. The actual data type does not matter. + public const int PropDirect = 0; + + /// The actual is an unordered array, the alias is to the first element of the array. + public const int PropArray = PropertyOptions.Array; + + /// The actual is an ordered array, the alias is to the first element of the array. + public const int PropArrayOrdered = PropertyOptions.ArrayOrdered; + + /// The actual is an alternate array, the alias is to the first element of the array. + public const int PropArrayAlternate = PropertyOptions.ArrayAlternate; + + /// The actual is an alternate text array, the alias is to the 'x-default' element of the array. + public const int PropArrayAltText = PropertyOptions.ArrayAltText; + + /// + public AliasOptions() + { + } + + /// the options to init with + /// If options are not consistant + public AliasOptions(int options) + : base(options) + { + } + + // EMPTY + /// Returns if the alias is of the simple form. + public bool IsSimple() + { + return GetOptions() == PropDirect; + } + + /// Returns the option. + public bool IsArray() + { + return GetOption(PropArray); + } + + /// the value to set + /// Returns the instance to call more set-methods. + public AliasOptions SetArray(bool value) + { + SetOption(PropArray, value); + return this; + } + + /// Returns the option. + public bool IsArrayOrdered() + { + return GetOption(PropArrayOrdered); + } + + /// the value to set + /// Returns the instance to call more set-methods. + public AliasOptions SetArrayOrdered(bool value) + { + SetOption(PropArray | PropArrayOrdered, value); + return this; + } + + /// Returns the option. + public bool IsArrayAlternate() + { + return GetOption(PropArrayAlternate); + } + + /// the value to set + /// Returns the instance to call more set-methods. + public AliasOptions SetArrayAlternate(bool value) + { + SetOption(PropArray | PropArrayOrdered | PropArrayAlternate, value); + return this; + } + + /// Returns the option. + public bool IsArrayAltText() + { + return GetOption(PropArrayAltText); + } + + /// the value to set + /// Returns the instance to call more set-methods. + public AliasOptions SetArrayAltText(bool value) + { + SetOption(PropArray | PropArrayOrdered | PropArrayAlternate | PropArrayAltText, value); + return this; + } + + /// + /// returns a + /// + /// s object + /// + /// If the options are not consistant. + public PropertyOptions ToPropertyOptions() + { + return new PropertyOptions(GetOptions()); + } + + /// + protected internal override string DefineOptionName(int option) + { + switch (option) + { + case PropDirect: + { + return "PROP_DIRECT"; + } + + case PropArray: + { + return "ARRAY"; + } + + case PropArrayOrdered: + { + return "ARRAY_ORDERED"; + } + + case PropArrayAlternate: + { + return "ARRAY_ALTERNATE"; + } + + case PropArrayAltText: + { + return "ARRAY_ALT_TEXT"; + } + + default: + { + return null; + } + } + } + + /// + protected internal override int GetValidOptions() + { + return PropDirect | PropArray | PropArrayOrdered | PropArrayAlternate | PropArrayAltText; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/options/IteratorOptions.cs b/Com.Adobe.Xmp/Com/adobe/xmp/options/IteratorOptions.cs index 77f5b5dd6..bc3acc8ef 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/options/IteratorOptions.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/options/IteratorOptions.cs @@ -1,134 +1,134 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -namespace Com.Adobe.Xmp.Options -{ - /// Options for XMPIterator construction. - /// 24.01.2006 - public sealed class IteratorOptions : Options - { - /// Just do the immediate children of the root, default is subtree. - public const int JustChildren = unchecked((int)(0x0100)); - - /// Just do the leaf nodes, default is all nodes in the subtree. - /// - /// Just do the leaf nodes, default is all nodes in the subtree. - /// Bugfix #2658965: If this option is set the Iterator returns the namespace - /// of the leaf instead of the namespace of the base property. - /// - public const int JustLeafnodes = unchecked((int)(0x0200)); - - /// Return just the leaf part of the path, default is the full path. - public const int JustLeafname = unchecked((int)(0x0400)); - - /// Omit all qualifiers. - public const int OmitQualifiers = unchecked((int)(0x1000)); - - // /** Include aliases, default is just actual properties. Note: Not supported. - // * @deprecated it is commonly preferred to work with the base properties */ - // public static final int INCLUDE_ALIASES = 0x0800; - /// Returns whether the option is set. - public bool IsJustChildren() - { - return GetOption(JustChildren); - } - - /// Returns whether the option is set. - public bool IsJustLeafname() - { - return GetOption(JustLeafname); - } - - /// Returns whether the option is set. - public bool IsJustLeafnodes() - { - return GetOption(JustLeafnodes); - } - - /// Returns whether the option is set. - public bool IsOmitQualifiers() - { - return GetOption(OmitQualifiers); - } - - /// Sets the option and returns the instance. - /// the value to set - /// Returns the instance to call more set-methods. - public IteratorOptions SetJustChildren(bool value) - { - SetOption(JustChildren, value); - return this; - } - - /// Sets the option and returns the instance. - /// the value to set - /// Returns the instance to call more set-methods. - public IteratorOptions SetJustLeafname(bool value) - { - SetOption(JustLeafname, value); - return this; - } - - /// Sets the option and returns the instance. - /// the value to set - /// Returns the instance to call more set-methods. - public IteratorOptions SetJustLeafnodes(bool value) - { - SetOption(JustLeafnodes, value); - return this; - } - - /// Sets the option and returns the instance. - /// the value to set - /// Returns the instance to call more set-methods. - public IteratorOptions SetOmitQualifiers(bool value) - { - SetOption(OmitQualifiers, value); - return this; - } - - /// - protected internal override string DefineOptionName(int option) - { - switch (option) - { - case JustChildren: - { - return "JUST_CHILDREN"; - } - - case JustLeafnodes: - { - return "JUST_LEAFNODES"; - } - - case JustLeafname: - { - return "JUST_LEAFNAME"; - } - - case OmitQualifiers: - { - return "OMIT_QUALIFIERS"; - } - - default: - { - return null; - } - } - } - - /// - protected internal override int GetValidOptions() - { - return JustChildren | JustLeafnodes | JustLeafname | OmitQualifiers; - } - } -} +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +namespace Com.Adobe.Xmp.Options +{ + /// Options for XMPIterator construction. + /// 24.01.2006 + public sealed class IteratorOptions : Options + { + /// Just do the immediate children of the root, default is subtree. + public const int JustChildren = unchecked((int)(0x0100)); + + /// Just do the leaf nodes, default is all nodes in the subtree. + /// + /// Just do the leaf nodes, default is all nodes in the subtree. + /// Bugfix #2658965: If this option is set the Iterator returns the namespace + /// of the leaf instead of the namespace of the base property. + /// + public const int JustLeafnodes = unchecked((int)(0x0200)); + + /// Return just the leaf part of the path, default is the full path. + public const int JustLeafname = unchecked((int)(0x0400)); + + /// Omit all qualifiers. + public const int OmitQualifiers = unchecked((int)(0x1000)); + + // /** Include aliases, default is just actual properties. Note: Not supported. + // * @deprecated it is commonly preferred to work with the base properties */ + // public static final int INCLUDE_ALIASES = 0x0800; + /// Returns whether the option is set. + public bool IsJustChildren() + { + return GetOption(JustChildren); + } + + /// Returns whether the option is set. + public bool IsJustLeafname() + { + return GetOption(JustLeafname); + } + + /// Returns whether the option is set. + public bool IsJustLeafnodes() + { + return GetOption(JustLeafnodes); + } + + /// Returns whether the option is set. + public bool IsOmitQualifiers() + { + return GetOption(OmitQualifiers); + } + + /// Sets the option and returns the instance. + /// the value to set + /// Returns the instance to call more set-methods. + public IteratorOptions SetJustChildren(bool value) + { + SetOption(JustChildren, value); + return this; + } + + /// Sets the option and returns the instance. + /// the value to set + /// Returns the instance to call more set-methods. + public IteratorOptions SetJustLeafname(bool value) + { + SetOption(JustLeafname, value); + return this; + } + + /// Sets the option and returns the instance. + /// the value to set + /// Returns the instance to call more set-methods. + public IteratorOptions SetJustLeafnodes(bool value) + { + SetOption(JustLeafnodes, value); + return this; + } + + /// Sets the option and returns the instance. + /// the value to set + /// Returns the instance to call more set-methods. + public IteratorOptions SetOmitQualifiers(bool value) + { + SetOption(OmitQualifiers, value); + return this; + } + + /// + protected internal override string DefineOptionName(int option) + { + switch (option) + { + case JustChildren: + { + return "JUST_CHILDREN"; + } + + case JustLeafnodes: + { + return "JUST_LEAFNODES"; + } + + case JustLeafname: + { + return "JUST_LEAFNAME"; + } + + case OmitQualifiers: + { + return "OMIT_QUALIFIERS"; + } + + default: + { + return null; + } + } + } + + /// + protected internal override int GetValidOptions() + { + return JustChildren | JustLeafnodes | JustLeafname | OmitQualifiers; + } + } +} diff --git a/Com.Adobe.Xmp/Com/adobe/xmp/options/Options.cs b/Com.Adobe.Xmp/Com/adobe/xmp/options/Options.cs index b8813d26b..a8bd2bb51 100644 --- a/Com.Adobe.Xmp/Com/adobe/xmp/options/Options.cs +++ b/Com.Adobe.Xmp/Com/adobe/xmp/options/Options.cs @@ -1,240 +1,240 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2006 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -using System.Collections; -using System.Text; -using Sharpen; - -namespace Com.Adobe.Xmp.Options -{ - /// The base class for a collection of 32 flag bits. - /// - /// The base class for a collection of 32 flag bits. Individual flags are defined as enum value bit - /// masks. Inheriting classes add convenience accessor methods. - /// - /// 24.01.2006 - public abstract class Options - { - /// the internal int containing all options - private int options = 0; - - /// a map containing the bit names - private IDictionary optionNames = null; - - /// The default constructor. - public Options() - { - } - - /// Constructor with the options bit mask. - /// the options bit mask - /// If the options are not correct - public Options(int options) - { - // EMTPY - AssertOptionsValid(options); - SetOptions(options); - } - - /// Resets the options. - public virtual void Clear() - { - options = 0; - } - - /// an option bitmask - /// Returns true, if this object is equal to the given options. - public virtual bool IsExactly(int optionBits) - { - return GetOptions() == optionBits; - } - - /// an option bitmask - /// Returns true, if this object contains all given options. - public virtual bool ContainsAllOptions(int optionBits) - { - return (GetOptions() & optionBits) == optionBits; - } - - /// an option bitmask - /// Returns true, if this object contain at least one of the given options. - public virtual bool ContainsOneOf(int optionBits) - { - return ((GetOptions()) & optionBits) != 0; - } - - /// the binary bit or bits that are requested - /// Returns if all of the requested bits are set or not. - protected internal virtual bool GetOption(int optionBit) - { - return (options & optionBit) != 0; - } - - /// the binary bit or bits that shall be set to the given value - /// the boolean value to set - public virtual void SetOption(int optionBits, bool value) - { - options = value ? options | optionBits : options & ~optionBits; - } - - /// Is friendly to access it during the tests. - /// Returns the options. - public virtual int GetOptions() - { - return options; - } - - /// The options to set. - /// - public virtual void SetOptions(int options) - { - AssertOptionsValid(options); - this.options = options; - } - - /// - public override bool Equals(object obj) - { - return GetOptions() == ((Options)obj).GetOptions(); - } - - /// - public override int GetHashCode() - { - return GetOptions(); - } - - /// Creates a human readable string from the set options. - /// - /// Creates a human readable string from the set options. Note: This method is quite - /// expensive and should only be used within tests or as - /// - /// - /// Returns a String listing all options that are set to true by their name, - /// like "option1 | option4". - /// - public virtual string GetOptionsString() - { - if (options != 0) - { - StringBuilder sb = new StringBuilder(); - int theBits = options; - while (theBits != 0) - { - int oneLessBit = theBits & (theBits - 1); - // clear rightmost one bit - int singleBit = theBits ^ oneLessBit; - string bitName = GetOptionName(singleBit); - sb.Append(bitName); - if (oneLessBit != 0) - { - sb.Append(" | "); - } - theBits = oneLessBit; - } - return sb.ToString(); - } - else - { - return ""; - } - } - - /// Returns the options as hex bitmask. - public override string ToString() - { - return "0x" + Extensions.ToHexString(options); - } - - /// To be implemeted by inheritants. - /// Returns a bit mask where all valid option bits are set. - protected internal abstract int GetValidOptions(); - - /// To be implemeted by inheritants. - /// a single, valid option bit. - /// Returns a human readable name for an option bit. - protected internal abstract string DefineOptionName(int option); - - /// The inheriting option class can do additional checks on the options. - /// - /// The inheriting option class can do additional checks on the options. - /// Note: For performance reasons this method is only called - /// when setting bitmasks directly. - /// When get- and set-methods are used, this method must be called manually, - /// normally only when the Options-object has been created from a client - /// (it has to be made public therefore). - /// - /// the bitmask to check. - /// Thrown if the options are not consistent. - protected internal virtual void AssertConsistency(int options) - { - } - - // empty, no checks - /// Checks options before they are set. - /// - /// Checks options before they are set. - /// First it is checked if only defined options are used, - /// second the additional - /// - /// -method is called. - /// - /// the options to check - /// Thrown if the options are invalid. - private void AssertOptionsValid(int options) - { - int invalidOptions = options & ~GetValidOptions(); - if (invalidOptions == 0) - { - AssertConsistency(options); - } - else - { - throw new XMPException("The option bit(s) 0x" + Extensions.ToHexString(invalidOptions) + " are invalid!", XMPErrorConstants.Badoptions); - } - } - - /// Looks up or asks the inherited class for the name of an option bit. - /// - /// Looks up or asks the inherited class for the name of an option bit. - /// Its save that there is only one valid option handed into the method. - /// - /// a single option bit - /// Returns the option name or undefined. - private string GetOptionName(int option) - { - IDictionary optionsNames = ProcureOptionNames(); - int key = option; - string result = (string)optionsNames.Get(key); - if (result == null) - { - result = DefineOptionName(option); - if (result != null) - { - optionsNames.Put(key, result); - } - else - { - result = "