/// <summary>Find or create a schema node if <c>createNodes</c> is true.</summary> /// <param name="tree">the root of the xmp tree.</param> /// <param name="namespaceUri">a namespace</param> /// <param name="suggestedPrefix">If a prefix is suggested, the namespace is allowed to be registered.</param> /// <param name="createNodes"> /// a flag indicating if the node shall be created if not found. /// <em>Note:</em> The namespace must be registered prior to this call. /// </param> /// <returns> /// Returns the schema node if found, <c>null</c> otherwise. /// Note: If <c>createNodes</c> is <c>true</c>, it is <b>always</b> /// returned a valid node. /// </returns> /// <exception cref="XmpException"> /// An exception is only thrown if an error occurred, not if a /// node was not found. /// </exception> internal static XmpNode FindSchemaNode(XmpNode tree, string namespaceUri, string suggestedPrefix, bool createNodes) { Debug.Assert(tree.Parent == null); // make sure that its the root var schemaNode = tree.FindChildByName(namespaceUri); if (schemaNode == null && createNodes) { var po = new PropertyOptions { IsSchemaNode = true }; schemaNode = new XmpNode(namespaceUri, po) { IsImplicit = true }; // only previously registered schema namespaces are allowed in the XMP tree. var prefix = XmpMetaFactory.SchemaRegistry.GetNamespacePrefix(namespaceUri); if (prefix == null) { if (!string.IsNullOrEmpty(suggestedPrefix)) { prefix = XmpMetaFactory.SchemaRegistry.RegisterNamespace(namespaceUri, suggestedPrefix); } else { throw new XmpException("Unregistered schema namespace URI", XmpErrorCode.BadSchema); } } schemaNode.Value = prefix; tree.AddChild(schemaNode); } return schemaNode; }
/// <summary> /// Tweak old XMP: Move an instance ID from rdf:about to the /// <em>xmpMM:InstanceID</em> property. /// </summary> /// <remarks> /// Tweak old XMP: Move an instance ID from rdf:about to the /// <em>xmpMM:InstanceID</em> 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 <em>xmpMM:InstanceID</em>, /// don't worry about any existing <em>xmpMM:InstanceID</em>. Both will /// only be present when a newer file with the <em>xmpMM:InstanceID</em> /// property is updated by an old app that uses <em>rdf:about</em>. /// </remarks> /// <param name="tree">the root of the metadata tree</param> /// <exception cref="XmpException">Thrown if tweaking fails.</exception> private static void TweakOldXmp(XmpNode tree) { if (tree.Name != null && tree.Name.Length >= Utils.UuidLength) { var nameStr = tree.Name.ToLower(); if (nameStr.StartsWith("uuid:")) { nameStr = nameStr.Substring (5); } if (Utils.CheckUuidFormat(nameStr)) { // move UUID to xmpMM:InstanceID and remove it from the root node var path = XmpPathParser.ExpandXPath(XmpConstants.NsXmpMm, "InstanceID"); var idNode = XmpNodeUtils.FindNode(tree, path, true, null); if (idNode != null) { idNode.Options = null; // Clobber any existing xmpMM:InstanceID. idNode.Value = "uuid:" + nameStr; idNode.RemoveChildren(); idNode.RemoveQualifiers(); tree.Name = null; } else { throw new XmpException("Failure creating xmpMM:InstanceID", XmpErrorCode.InternalFailure); } } } }
/// <summary>The outermost call is special.</summary> /// <remarks> /// 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. /// </remarks> /// <param name="aliasNode">the alias node</param> /// <param name="baseNode">the base node of the alias</param> /// <param name="outerCall">marks the outer call of the recursion</param> /// <exception cref="XmpException">Forwards XMP errors</exception> private static void CompareAliasedSubtrees(XmpNode aliasNode, XmpNode baseNode, bool outerCall) { if (!aliasNode.Value.Equals(baseNode.Value) || aliasNode.GetChildrenLength() != baseNode.GetChildrenLength()) { throw new XmpException("Mismatch between alias and base nodes", XmpErrorCode.BadXmp); } if (!outerCall && (!aliasNode.Name.Equals(baseNode.Name) || !aliasNode.Options.Equals(baseNode.Options) || aliasNode.GetQualifierLength() != baseNode.GetQualifierLength())) { throw new XmpException("Mismatch between alias and base nodes", XmpErrorCode.BadXmp); } for (IIterator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.HasNext() && bn.HasNext(); ) { var aliasChild = (XmpNode)an.Next(); var baseChild = (XmpNode)bn.Next(); CompareAliasedSubtrees(aliasChild, baseChild, false); } for (IIterator an1 = aliasNode.IterateQualifier(), bn1 = baseNode.IterateQualifier(); an1.HasNext() && bn1.HasNext(); ) { var aliasQual = (XmpNode)an1.Next(); var baseQual = (XmpNode)bn1.Next(); CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <summary> /// The initial support for WAV files mapped a legacy ID3 audio copyright /// into a new xmpDM:copyright property. /// </summary> /// <remarks> /// 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: /// <pre> /// 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. /// </pre> /// </remarks> /// <param name="xmp">the metadata object</param> /// <param name="dmCopyright">the "dm:copyright"-property</param> private static void MigrateAudioCopyright(IXmpMeta xmp, XmpNode dmCopyright) { try { var dcSchema = XmpNodeUtils.FindSchemaNode(((XmpMeta)xmp).GetRoot(), XmpConstants.NsDC, true); var dmValue = dmCopyright.Value; var doubleLf = "\n\n"; var 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(XmpConstants.NsDC, "rights", string.Empty, XmpConstants.XDefault, dmValue, null); } else { var xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConstants.XDefault); if (xdIndex < 0) { // 2. No x-default item, create from the first item. var firstValue = dcRightsArray.GetChild(1).Value; xmp.SetLocalizedText(XmpConstants.NsDC, "rights", string.Empty, XmpConstants.XDefault, firstValue, null); xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConstants.XDefault); } // 3. Look for a double linefeed in the x-default value. var defaultNode = dcRightsArray.GetChild(xdIndex); var defaultValue = defaultNode.Value; var 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.Value = defaultValue + doubleLf + dmValue; } } else { // 3B. Has double LF, compare the tail. if (!defaultValue.Substring (lfPos + 2).Equals(dmValue)) { // 3B2. Replace the x-default tail. defaultNode.Value = defaultValue.Substring (0, lfPos + 2 - 0) + dmValue; } } } // 4. Get rid of the xmpDM:copyright. dmCopyright.Parent.RemoveChild(dmCopyright); } catch (XmpException) { } }
/// <summary>Write each of the parent's simple unqualified properties as an attribute.</summary> /// <remarks> /// Write each of the parent's simple unqualified properties as an attribute. Returns true if all /// of the properties are written as attributes. /// </remarks> /// <param name="parentNode">the parent property node</param> /// <param name="indent">the current indent level</param> /// <returns>Returns true if all properties can be rendered as RDF attribute.</returns> /// <exception cref="System.IO.IOException"/> private bool SerializeCompactRdfAttrProps(XmpNode parentNode, int indent) { var allAreAttrs = true; for (var it = parentNode.IterateChildren(); it.HasNext(); ) { var prop = (XmpNode)it.Next(); if (CanBeRdfAttrProp(prop)) { WriteNewline(); WriteIndent(indent); Write(prop.Name); Write("=\""); AppendNodeValue(prop.Value, true); Write('"'); } else { allAreAttrs = false; } } return allAreAttrs; }
/// <summary>Visit all of the top level nodes looking for aliases.</summary> /// <remarks> /// 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. /// </remarks> /// <param name="tree">the root of the metadata tree</param> /// <param name="options">th parsing options</param> /// <exception cref="XmpException">Forwards XMP errors</exception> private static void MoveExplicitAliases(XmpNode tree, ParseOptions options) { if (!tree.HasAliases) { return; } tree.HasAliases = false; var strictAliasing = options.StrictAliasing; for (var schemaIt = tree.GetUnmodifiableChildren().Iterator(); schemaIt.HasNext(); ) { var currSchema = (XmpNode)schemaIt.Next(); if (!currSchema.HasAliases) { continue; } for (var propertyIt = currSchema.IterateChildren(); propertyIt.HasNext(); ) { var currProp = (XmpNode)propertyIt.Next(); if (!currProp.IsAlias) { continue; } currProp.IsAlias = false; // Find the base path, look for the base schema and root node. var info = XmpMetaFactory.SchemaRegistry.FindAlias(currProp.Name); if (info != null) { // find or create schema var baseSchema = XmpNodeUtils.FindSchemaNode(tree, info.Namespace, null, true); baseSchema.IsImplicit = false; var baseNode = XmpNodeUtils.FindChildNode(baseSchema, info.Prefix + info.PropName, false); if (baseNode == null) { if (info.AliasForm.IsSimple()) { // A top-to-top alias, transplant the property. // change the alias property name to the base name var qname = info.Prefix + info.PropName; currProp.Name = 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.Prefix + info.PropName, info.AliasForm.ToPropertyOptions()); baseSchema.AddChild(baseNode); TransplantArrayItemAlias(propertyIt, currProp, baseNode); } } else if (info.AliasForm.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.AliasForm.IsArrayAltText) { var xdIndex = XmpNodeUtils.LookupLanguageItem(baseNode, XmpConstants.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.HasAliases = false; } }
/// <summary>Fixes the GPS Timestamp in EXIF.</summary> /// <param name="exifSchema">the EXIF schema node</param> /// <exception cref="XmpException">Thrown if the date conversion fails.</exception> private static void FixGpsTimeStamp(XmpNode exifSchema) { // Note: if dates are not found the convert-methods throws an exceptions, // and this methods returns. var gpsDateTime = XmpNodeUtils.FindChildNode(exifSchema, "exif:GPSTimeStamp", false); if (gpsDateTime == null) return; try { var binGpsStamp = XmpCore.XmpUtils.ConvertToDate(gpsDateTime.Value); if (binGpsStamp.Year != 0 || binGpsStamp.Month != 0 || binGpsStamp.Day != 0) return; var otherDate = XmpNodeUtils.FindChildNode(exifSchema, "exif:DateTimeOriginal", false) ?? XmpNodeUtils.FindChildNode(exifSchema, "exif:DateTimeDigitized", false); var binOtherDate = XmpCore.XmpUtils.ConvertToDate(otherDate.Value); var cal = binGpsStamp.Calendar; cal.Set(CalendarEnum.Year, binOtherDate.Year); cal.Set(CalendarEnum.Month, binOtherDate.Month); cal.Set(CalendarEnum.DayOfMonth, binOtherDate.Day); binGpsStamp = new XmpDateTime(cal); gpsDateTime.Value = XmpCore.XmpUtils.ConvertFromDate(binGpsStamp); } catch (XmpException) { // Don't let a missing or bad date stop other things. } }
/// <summary> /// The internals for setProperty() and related calls, used after the node is /// found or created. /// </summary> /// <param name="node">the newly created node</param> /// <param name="value">the node value, can be <c>null</c></param> /// <param name="newOptions">options for the new node, must not be <c>null</c>.</param> /// <param name="deleteExisting">flag if the existing value is to be overwritten</param> /// <exception cref="XmpException">thrown if options and value do not correspond</exception> internal 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.Options.MergeWith(newOptions); if (!node.Options.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", XmpErrorCode.BadXPath); } node.RemoveChildren(); } }
/// <summary> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br /> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. /// </summary> /// <remarks> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br /> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. <br /> /// The <c>xml:lang</c> attribute was dropped from an /// <c>alt-text</c> item if the language was <c>x-default</c>. /// </remarks> /// <param name="dcSchema">the DC schema node</param> /// <exception cref="XmpException">Thrown if normalization fails</exception> private static void NormalizeDcArrays(XmpNode dcSchema) { for (var i = 1; i <= dcSchema.GetChildrenLength(); i++) { var currProp = dcSchema.GetChild(i); var arrayForm = (PropertyOptions)_dcArrayForms[currProp.Name]; if (arrayForm == null) { continue; } if (currProp.Options.IsSimple) { // create a new array and add the current property as child, // if it was formerly simple var newArray = new XmpNode(currProp.Name, arrayForm); currProp.Name = XmpConstants.ArrayItemName; newArray.AddChild(currProp); dcSchema.ReplaceChild(i, newArray); // fix language alternatives if (arrayForm.IsArrayAltText && !currProp.Options.HasLanguage) { var newLang = new XmpNode(XmpConstants.XmlLang, XmpConstants.XDefault, null); currProp.AddQualifier(newLang); } } else { // clear array options and add corrected array form if it has been an array before currProp.Options.SetOption(PropertyOptions.ArrayFlag | PropertyOptions.ArrayOrderedFlag | PropertyOptions.ArrayAlternateFlag | PropertyOptions.ArrayAltTextFlag, false); currProp.Options.MergeWith(arrayForm); if (arrayForm.IsArrayAltText) { // applying for "dc:description", "dc:rights", "dc:title" RepairAltText(currProp); } } } }
public XmpPropertyInfo450(XmpNode node, string baseNs, string path, string value) { _node = node; _baseNs = baseNs; Path = path; Value = value; }
/// <summary>Constructor for a cloned metadata tree.</summary> /// <param name="tree"> /// an prefilled metadata tree which fulfills all /// <c>XMPNode</c> contracts. /// </param> public XmpMeta(XmpNode tree) { _tree = tree; }
/// <param name="currNode">the node that will be added to the path.</param> /// <param name="parentPath">the path up to this node.</param> /// <param name="currentIndex">the current array index if an array is traversed</param> /// <returns>Returns the updated path.</returns> protected string AccumulatePath(XmpNode currNode, string parentPath, int currentIndex) { string separator; string segmentName; if (currNode.Parent == null || currNode.Options.IsSchemaNode) { return null; } if (currNode.Parent.Options.IsArray) { separator = string.Empty; segmentName = "[" + currentIndex + "]"; } else { separator = "/"; segmentName = currNode.Name; } if (string.IsNullOrEmpty(parentPath)) { return segmentName; } if (_enclosing.Options.IsJustLeafName) { return !segmentName.StartsWith("?") ? segmentName : segmentName.Substring (1); } // qualifier return parentPath + separator + segmentName; }
/// <summary>Creates a property info object from an <c>XMPNode</c>.</summary> /// <param name="node">an <c>XMPNode</c></param> /// <param name="baseNs">the base namespace to report</param> /// <param name="path">the full property path</param> /// <returns>Returns a <c>XMPProperty</c>-object that serves representation of the node.</returns> protected static IXmpPropertyInfo CreatePropertyInfo(XmpNode node, string baseNs, string path) { var value = node.Options.IsSchemaNode ? null : node.Value; return new XmpPropertyInfo450(node, baseNs, path, value); }
/// <summary>Constructor for the node iterator.</summary> /// <param name="enclosing"></param> /// <param name="visitedNode">the currently visited node</param> /// <param name="parentPath">the accumulated path of the node</param> /// <param name="index">the index within the parent node (only for arrays)</param> public NodeIterator(XmpIterator enclosing, XmpNode visitedNode, string parentPath, int index) { _enclosing = enclosing; _visitedNode = visitedNode; _state = IterateNode; if (visitedNode.Options.IsSchemaNode) { _enclosing.BaseNamespace = visitedNode.Name; } // for all but the root node and schema nodes _path = AccumulatePath(visitedNode, parentPath, index); }
/// <summary>Serializes the general qualifier.</summary> /// <param name="node">the root node of the subtree</param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">Forwards all writer exceptions.</exception> /// <exception cref="XmpException">If qualifier and element fields are mixed.</exception> 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 (var iq = node.IterateQualifier(); iq.HasNext(); ) { var qualifier = (XmpNode)iq.Next(); SerializeCanonicalRdfProperty(qualifier, false, false, indent + 1); } }
/// <summary> /// Recursively handles the "value" for a node that must be written as an RDF /// property element. /// </summary> /// <remarks> /// 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. /// <blockquote> /// <pre> /// <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> /// </pre> /// </blockquote> /// *** Consider numbered array items, but has compatibility problems. /// Consider qualified form with rdf:Description and attributes. /// </remarks> /// <param name="parentNode">the parent node</param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">Forwards writer exceptions</exception> /// <exception cref="XmpException">If qualifier and element fields are mixed.</exception> private void SerializeCompactRdfElementProps(XmpNode parentNode, int indent) { for (var it = parentNode.IterateChildren(); it.HasNext(); ) { var node = (XmpNode)it.Next(); if (CanBeRdfAttrProp(node)) { continue; } var emitEndTag = true; var 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. var elemName = node.Name; if (XmpConstants.ArrayItemName.Equals(elemName)) { elemName = "rdf:li"; } WriteIndent(indent); Write('<'); Write(elemName); var hasGeneralQualifiers = false; var hasRdfResourceQual = false; for (var iq = node.IterateQualifier(); iq.HasNext(); ) { var qualifier = (XmpNode)iq.Next(); if (!RdfAttrQualifier.Contains(qualifier.Name)) { hasGeneralQualifiers = true; } else { hasRdfResourceQual = "rdf:resource".Equals(qualifier.Name); Write(' '); Write(qualifier.Name); Write("=\""); AppendNodeValue(qualifier.Value, 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.Options.IsCompositeProperty) { var result = SerializeCompactRdfSimpleProp(node); emitEndTag = ((bool)result[0]); indentEndTag = ((bool)result[1]); } else { if (node.Options.IsArray) { SerializeCompactRdfArrayProp(node, indent); } else { emitEndTag = SerializeCompactRdfStructProp(node, indent, hasRdfResourceQual); } } } // Emit the property element end tag. if (emitEndTag) { if (indentEndTag) { WriteIndent(indent); } Write("</"); Write(elemName); Write('>'); WriteNewline(); } } }
/// <summary>Constructor for an empty metadata object.</summary> public XmpMeta() { // create root node _tree = new XmpNode(null, null, null); }
/// <summary>Constructor</summary> /// <param name="enclosing"></param> /// <param name="parentNode">the node which children shall be iterated.</param> /// <param name="parentPath">the full path of the former node without the leaf node.</param> public NodeIteratorChildren(XmpIterator enclosing, XmpNode parentNode, string parentPath) : base(enclosing) { _enclosing = enclosing; if (parentNode.Options.IsSchemaNode) { _enclosing.BaseNamespace = parentNode.Name; } _parentPath = AccumulatePath(parentNode, parentPath, 1); _childrenIterator = parentNode.IterateChildren(); }
public XmpProperty682(object value, XmpNode propNode) { _value = value; _propNode = propNode; }
/// <param name="source">The source XMP object.</param> /// <param name="destination">The destination XMP object.</param> /// <param name="doAllProperties">Do internal properties in addition to external properties.</param> /// <param name="replaceOldValues">Replace the values of existing properties.</param> /// <param name="deleteEmptyValues">Delete destination values if source property is empty.</param> /// <exception cref="XmpException">Forwards the Exceptions from the metadata processing</exception> public static void AppendProperties(IXmpMeta source, IXmpMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) { ParameterAsserts.AssertImplementation(source); ParameterAsserts.AssertImplementation(destination); var src = (XmpMeta)source; var dest = (XmpMeta)destination; for (var it = src.GetRoot().IterateChildren(); it.HasNext(); ) { var sourceSchema = (XmpNode)it.Next(); // Make sure we have a destination schema node var destSchema = XmpNodeUtils.FindSchemaNode(dest.GetRoot(), sourceSchema.Name, false); var createdSchema = false; if (destSchema == null) { destSchema = new XmpNode(sourceSchema.Name, sourceSchema.Value, new PropertyOptions { IsSchemaNode = true }); dest.GetRoot().AddChild(destSchema); createdSchema = true; } // Process the source schema's children. for (var ic = sourceSchema.IterateChildren(); ic.HasNext(); ) { var sourceProp = (XmpNode)ic.Next(); if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.Name, sourceProp.Name)) AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); } if (!destSchema.HasChildren && (createdSchema || deleteEmptyValues)) { // Don't create an empty schema / remove empty schema. dest.GetRoot().RemoveChild(destSchema); } } }
/// <summary>Locate or create the item node and set the value.</summary> /// <remarks> /// 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. /// </remarks> /// <param name="arrayNode">an array node</param> /// <param name="itemIndex">the index where to insert the item</param> /// <param name="itemValue">the item value</param> /// <param name="itemOptions">the options for the new item</param> /// <param name="insert">insert oder overwrite at index position?</param> /// <exception cref="XmpException"/> private void DoSetArrayItem(XmpNode arrayNode, int itemIndex, string itemValue, PropertyOptions itemOptions, bool insert) { var itemNode = new XmpNode(XmpConstants.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. var maxIndex = insert ? arrayNode.GetChildrenLength() + 1 : arrayNode.GetChildrenLength(); if (itemIndex == XmpConstants.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", XmpErrorCode.BadIndex); } }
/// <summary>Remove all schema children according to the flag <c>doAllProperties</c>.</summary> /// <remarks>Empty schemas are automatically remove by <c>XMPNode</c>.</remarks> /// <param name="schemaNode">a schema node</param> /// <param name="doAllProperties">flag if all properties or only externals shall be removed.</param> /// <returns>Returns true if the schema is empty after the operation.</returns> private static bool RemoveSchemaChildren(XmpNode schemaNode, bool doAllProperties) { for (var it = schemaNode.IterateChildren(); it.HasNext(); ) { var currProp = (XmpNode)it.Next(); if (doAllProperties || !Utils.IsInternalProperty(schemaNode.Name, currProp.Name)) { it.Remove(); } } return !schemaNode.HasChildren; }
/// <summary> /// Evaluates a raw node value to the given value type, apply special /// conversions for defined types in XMP. /// </summary> /// <param name="valueType">an int indicating the value type</param> /// <param name="propNode">the node containing the value</param> /// <returns>Returns a literal value for the node.</returns> /// <exception cref="XmpException"/> private static object EvaluateNodeValue(ValueType valueType, XmpNode propNode) { object value; var rawValue = propNode.Value; switch (valueType) { case ValueType.Boolean: { value = XmpCore.XmpUtils.ConvertToBoolean(rawValue); break; } case ValueType.Integer: { value = XmpCore.XmpUtils.ConvertToInteger(rawValue); break; } case ValueType.Long: { value = XmpCore.XmpUtils.ConvertToLong(rawValue); break; } case ValueType.Double: { value = XmpCore.XmpUtils.ConvertToDouble(rawValue); break; } case ValueType.Date: { value = XmpCore.XmpUtils.ConvertToDate(rawValue); break; } case ValueType.Calendar: { var dt = XmpCore.XmpUtils.ConvertToDate(rawValue); value = dt.Calendar; break; } case ValueType.Base64: { value = XmpCore.XmpUtils.DecodeBase64(rawValue); break; } case ValueType.String: 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.Options.IsCompositeProperty ? rawValue : string.Empty; break; } } return value; }
/// <param name="destXmp">The destination XMP object.</param> /// <param name="sourceNode">the source node</param> /// <param name="destParent">the parent of the destination node</param> /// <param name="replaceOldValues">Replace the values of existing properties.</param> /// <param name="deleteEmptyValues">flag if properties with empty values should be deleted in the destination object.</param> /// <exception cref="XmpException"/> private static void AppendSubtree(XmpMeta destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues) { var destNode = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false); var valueIsEmpty = false; if (deleteEmptyValues) valueIsEmpty = sourceNode.Options.IsSimple ? string.IsNullOrEmpty(sourceNode.Value) : !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.Value, sourceNode.Options, 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. var sourceForm = sourceNode.Options; var destForm = destNode.Options; 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 (var it = sourceNode.IterateChildren(); it.HasNext(); ) { var 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 (var it = sourceNode.IterateChildren(); it.HasNext(); ) { var sourceItem = (XmpNode)it.Next(); if (!sourceItem.HasQualifier || !XmpConstants.XmlLang.Equals(sourceItem.GetQualifier(1).Name)) continue; var destIndex = XmpNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).Value); if (deleteEmptyValues && string.IsNullOrEmpty(sourceItem.Value)) { if (destIndex != -1) { destNode.RemoveChild(destIndex); if (!destNode.HasChildren) destParent.RemoveChild(destNode); } } else if (destIndex == -1) { // Not replacing, keep the existing item. if (!XmpConstants.XDefault.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren) { sourceItem.CloneSubtree(destNode); } else { var destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options); 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 (var children = sourceNode.IterateChildren(); children.HasNext(); ) { var sourceItem = (XmpNode)children.Next(); var match = false; for (var id = destNode.IterateChildren(); id.HasNext(); ) { var destItem = (XmpNode)id.Next(); if (ItemValuesMatch(sourceItem, destItem)) match = true; } if (!match) { destNode = (XmpNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } } } }
/// <summary>Make sure that the array is well-formed AltText.</summary> /// <remarks> /// 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". /// </remarks> /// <param name="arrayNode">the property node of the array to repair.</param> /// <exception cref="XmpException">Forwards unexpected exceptions.</exception> private static void RepairAltText(XmpNode arrayNode) { if (arrayNode == null || !arrayNode.Options.IsArray) { // Already OK or not even an array. return; } // fix options arrayNode.Options.IsArrayOrdered = true; arrayNode.Options.IsArrayAlternate = true; arrayNode.Options.IsArrayAltText = true; for (var it = arrayNode.IterateChildren(); it.HasNext(); ) { var currChild = (XmpNode)it.Next(); if (currChild.Options.IsCompositeProperty) { // Delete non-simple children. it.Remove(); } else if (!currChild.Options.HasLanguage) { if (string.IsNullOrEmpty(currChild.Value)) { // Delete empty valued children that have no xml:lang. it.Remove(); } else { // Add an xml:lang qualifier with the value "x-repair". var repairLang = new XmpNode(XmpConstants.XmlLang, "x-repair", null); currChild.AddQualifier(repairLang); } } } }
/// <summary>Compares two nodes including its children and qualifier.</summary> /// <param name="leftNode">an <c>XMPNode</c></param> /// <param name="rightNode">an <c>XMPNode</c></param> /// <returns>Returns true if the nodes are equal, false otherwise.</returns> /// <exception cref="XmpException">Forwards exceptions to the calling method.</exception> private static bool ItemValuesMatch(XmpNode leftNode, XmpNode rightNode) { var leftForm = leftNode.Options; var rightForm = rightNode.Options; if (leftForm.Equals(rightForm)) return false; if (leftForm.GetOptions() == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.Value.Equals(rightNode.Value)) return false; if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage) return false; if (leftNode.Options.HasLanguage && !leftNode.GetQualifier(1).Value.Equals(rightNode.GetQualifier(1).Value)) return false; } else { if (leftForm.IsStruct) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.GetChildrenLength() != rightNode.GetChildrenLength()) return false; for (var it = leftNode.IterateChildren(); it.HasNext(); ) { var leftField = (XmpNode)it.Next(); var rightField = XmpNodeUtils.FindChildNode(rightNode, leftField.Name, 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 (var il = leftNode.IterateChildren(); il.HasNext(); ) { var leftItem = (XmpNode)il.Next(); var match = false; for (var ir = rightNode.IterateChildren(); ir.HasNext(); ) { var rightItem = (XmpNode)ir.Next(); if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) return false; } } } return true; }
/// <summary>Moves an alias node of array form to another schema into an array</summary> /// <param name="propertyIt">the property iterator of the old schema (used to delete the property)</param> /// <param name="childNode">the node to be moved</param> /// <param name="baseArray">the base array for the array item</param> /// <exception cref="XmpException">Forwards XMP errors</exception> private static void TransplantArrayItemAlias(IIterator propertyIt, XmpNode childNode, XmpNode baseArray) { if (baseArray.Options.IsArrayAltText) { if (childNode.Options.HasLanguage) { throw new XmpException("Alias to x-default already has a language qualifier", XmpErrorCode.BadXmp); } var langQual = new XmpNode(XmpConstants.XmlLang, XmpConstants.XDefault, null); childNode.AddQualifier(langQual); } propertyIt.Remove(); childNode.Name = XmpConstants.ArrayItemName; baseArray.AddChild(childNode); }
static bool HasLangQualifier(XmpNode node, string lang) { var qualifier = node.GetQualifier(XmpTag.XML_NS, "lang"); return(qualifier != null && qualifier.Value == lang); }
/// <summary>Remove all empty schemas from the metadata tree that were generated during the rdf parsing.</summary> /// <param name="tree">the root of the metadata tree</param> private static void DeleteEmptySchemas(XmpNode tree) { // Delete empty schema nodes. Do this last, other cleanup can make empty // schema. for (var it = tree.IterateChildren(); it.HasNext(); ) { var schema = (XmpNode)it.Next(); if (!schema.HasChildren) { it.Remove(); } } }
public XmpProperty407(XmpNode itemNode) { _itemNode = itemNode; }
static void Main(string[] args) { if (args.Length != 3) { System.Console.WriteLine("The correct number of arguments is 3"); System.Console.WriteLine("Description:"); System.Console.WriteLine("Metimpdf is the program for [met]adata [im]port into a [pdf] file"); System.Console.WriteLine("as a part of the Math-Net.Ru Meta Data Creator Tools."); System.Console.WriteLine("Use:"); System.Console.WriteLine("metimpdf <pdf file> <metadata> <output pdf file>"); System.Console.WriteLine("Example:"); System.Console.WriteLine("metimpdf source.pdf meta.data output.pdf"); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); return; } if (args.Length == 3) { //System.Console.WriteLine(args[0]); //System.Console.WriteLine(args[1]); //System.Console.WriteLine(args[2]); try{ string SourcePdfFile = args[0]; string MetadataFile = args[1]; string OutputPdfFile = args[2]; string prismSchema = "http://prismstandard.org/namespaces/basic/2.2/"; // Создаем и регистрируем несуществующую в системе схему prism XmpMetaFactory.SchemaRegistry.RegisterNamespace(prismSchema, "prism"); /** * Создаём карту для корректной конвертации и вставки значений в metadata * т.к. ключевые слова указанные в .data файле не существуют в пространствах */ List <MapObject> map = new List <MapObject>(); map.Add(new MapObject(XmpConst.NS_DC, "Title", "title", ValueType.Alt)); map.Add(new MapObject(XmpConst.NS_DC, "Author", "creator", ValueType.Seq, false)); map.Add(new MapObject(XmpConst.NS_DC, "Keywords", "subject", ValueType.Bag, false)); map.Add(new MapObject(XmpConst.NS_DC, "Doi", "identifier", ValueType.Property)); map.Add(new MapObject(XmpConst.NS_DC, "Publisher", "publisher", ValueType.Bag)); map.Add(new MapObject(XmpConst.NS_DC, "Rights", "rights", ValueType.Alt)); map.Add(new MapObject(XmpConst.NS_DC, "Description", "description", ValueType.Alt)); map.Add(new MapObject(XmpConst.NS_PDF, "Keywords", ValueType.Property)); map.Add(new MapObject(XmpConst.NS_PDF, "Producer", ValueType.Property)); map.Add(new MapObject(XmpConst.NS_XMP, "CreatorTool", ValueType.Property)); map.Add(new MapObject(XmpConst.NS_XMP_RIGHTS, "Rights", "UsageTerms", ValueType.Alt)); map.Add(new MapObject(XmpConst.NS_XMP_RIGHTS, "Marked", "Marked", ValueType.Property)); map.Add(new MapObject(XmpConst.NS_XMP_RIGHTS, "RightsUrl", "WebStatement", ValueType.Property)); map.Add(new MapObject(prismSchema, "Doi", "doi", ValueType.Property)); map.Add(new MapObject(prismSchema, "DoiUrl", "url", ValueType.Property)); map.Add(new MapObject(prismSchema, "Issn", "issn", ValueType.Property)); map.Add(new MapObject(prismSchema, "eIssn", "eissn", ValueType.Property)); map.Add(new MapObject(prismSchema, "Volume", "volume", ValueType.Property)); map.Add(new MapObject(prismSchema, "Number", "number", ValueType.Property)); map.Add(new MapObject(prismSchema, "CoverDisplayDate", "coverDisplayDate", ValueType.Property)); map.Add(new MapObject(prismSchema, "CoverDate", "coverDate", ValueType.Property)); map.Add(new MapObject(prismSchema, "IssueName", "issueName", ValueType.Property)); map.Add(new MapObject(prismSchema, "PageRange", "pageRange", ValueType.Property)); map.Add(new MapObject(prismSchema, "StartingPage", "startingPage", ValueType.Property)); map.Add(new MapObject(prismSchema, "EndingPage", "endingPage", ValueType.Property)); map.Add(new MapObject(prismSchema, "AggregationType", "aggregationType", ValueType.Property)); map.Add(new MapObject(prismSchema, "Platform", "originPlatform", ValueType.Property)); map.Add(new MapObject(prismSchema, "PublicationName", "publicationName", ValueType.Property)); map.Add(new MapObject(prismSchema, "Edition", "edition", ValueType.Property)); map.Add(new MapObject(prismSchema, "Section", "section", ValueType.Property)); map.Add(new MapObject(prismSchema, "Subsection1", "subsection1", ValueType.Property)); map.Add(new MapObject(prismSchema, "Rights", "copyright", ValueType.Property)); // Открываем поток для чтения/записи значений using (FileStream fs = new FileStream("metadata.xmp", FileMode.Create, FileAccess.ReadWrite)){ // Считываем все стрроки из .data файла string[] lines = File.ReadAllLines(MetadataFile); // Создаем словарь разбора строк в сопоставлении ключ:значение Dictionary <string, string> info = new Dictionary <string, string>(); // цикл перебора строк для разбора в словарь ключ:значение foreach (string line in lines) { string[] splitted = line.Split('='); // делим строку попалам относительно знака = string key = splitted[0].Trim(); // ключ(значение слева от = ) string value = splitted[1].Trim(); // значение(значение справа от =) value = value.Substring(1, value.Length - 2); // убираем скобки {} info.Add(key, value); } /** * Создаём объект для корректной современной записи Xmp и в качестве значения конструктора * передаём только поток */ XmpWriter xmpWriter = new XmpWriter(fs); xmpWriter.XmpMeta.SetProperty(prismSchema, "platform", "print"); xmpWriter.XmpMeta.SetProperty(prismSchema, "edition", "My print"); xmpWriter.XmpMeta.SetProperty(prismSchema, "section", "My print"); xmpWriter.XmpMeta.SetProperty(prismSchema, "subsection1", "My print"); // цикл перебора словаря для дальнейшего разбора foreach (string key in info.Keys) { // цикл перебора карты на поиск соответствующих ключевым словам объектов foreach (MapObject p in map) { if (p.Name == key) // если имя объекта равно ключевому слову из data то /** * сохраняем значение в xmpWriter с помощь специального * упращенного метода сохранения(сам метод создан в конце класса) */ { SetValue(xmpWriter, p, info[key]); } } } // модификатор языка XmpNode languageQualifier = new XmpNode("xml:lang", "x-default", new PropertyOptions { Qualifier = true }); // более низкий уровень взаимодействия с деревом xml для проставки дополнительных атрибутов XmpNode groupNode = (xmpWriter.XmpMeta as XmpMetaImpl).Root.FindChildByName(XmpConst.NS_DC); // находим элементы которым нужно указать языковую конструкцию XmpNode titleDcNode = groupNode.FindChildByName("dc:title"); XmpNode rightsDcNode = groupNode.FindChildByName("dc:rights"); XmpNode descriptyopmDcNode = groupNode.FindChildByName("dc:description"); // проставляем модификатор titleDcNode.GetChild(1).AddQualifier(languageQualifier); rightsDcNode.GetChild(1).AddQualifier(languageQualifier); descriptyopmDcNode.GetChild(1).AddQualifier(languageQualifier); groupNode = (xmpWriter.XmpMeta as XmpMetaImpl).Root.FindChildByName(XmpConst.NS_XMP_RIGHTS); XmpNode rightsXmpRightsNode = groupNode.FindChildByName("xmpRights:UsageTerms"); rightsXmpRightsNode.GetChild(1).AddQualifier(languageQualifier); // Закрываем xmpWriter xmpWriter.Close(); } // после этой скобочки файл metadata.xmp сохранится и запишется автоматически // Внедрение metadata.xmp в SourcePdfFile и вывод в OutputPdfFile PdfReader reader = new PdfReader(SourcePdfFile); PdfStamper stamper = new PdfStamper(reader, new FileStream(OutputPdfFile, FileMode.Create)); using (FileStream fs = File.OpenRead("metadata.xmp")){ byte[] buffer = new byte[fs.Length]; fs.Read(buffer, 0, buffer.Length); stamper.XmpMetadata = buffer; } stamper.Close(); reader.Close(); } // Catching iTextSharp.text.DocumentException if any catch (DocumentException de) { throw de; } // Catching System.IO.IOException if any catch (IOException ioe) { throw ioe; } } Console.Write("Press any key to continue . . . "); Console.ReadKey(true); }
/// <summary>Serializes an array property.</summary> /// <param name="node">an XMPNode</param> /// <param name="indent">the current indent level</param> /// <exception cref="System.IO.IOException">Forwards the writer exceptions.</exception> /// <exception cref="XmpException">If qualifier and element fields are mixed.</exception> private void SerializeCompactRdfArrayProp(XmpNode node, int indent) { // This is an array. Write('>'); WriteNewline(); EmitRdfArrayTag(node, true, indent + 1); if (node.Options.IsArrayAltText) { XmpNodeUtils.NormalizeLangArray(node); } SerializeCompactRdfElementProps(node, indent + 2); EmitRdfArrayTag(node, false, indent + 1); }