static private FindChildNode ( |
||
parent | /// the parent node | |
childName | string | /// the node name to find |
createNodes | bool | /// flag, if new nodes shall be created. |
Résultat |
/// <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. XmpNode gpsDateTime = XmpNodeUtils.FindChildNode(exifSchema, "exif:GPSTimeStamp", false); if (gpsDateTime == null) { return; } try { XMPDateTime binGpsStamp = XMPUtils.ConvertToDate(gpsDateTime.Value); if (binGpsStamp.Year != 0 || binGpsStamp.Month != 0 || binGpsStamp.Day != 0) { return; } XmpNode otherDate = XmpNodeUtils.FindChildNode(exifSchema, "exif:DateTimeOriginal", false); otherDate = otherDate ?? XmpNodeUtils.FindChildNode(exifSchema, "exif:DateTimeDigitized", false); XMPDateTime binOtherDate = XMPUtils.ConvertToDate(otherDate.Value); XmpCalendar cal = binGpsStamp.Calendar; DateTime dt = new DateTime(binOtherDate.Year, binOtherDate.Month, binOtherDate.Day, cal.DateTime.Hour, cal.DateTime.Minute, cal.DateTime.Second, cal.DateTime.Millisecond); cal.DateTime = dt; binGpsStamp = new XmpDateTimeImpl(cal); gpsDateTime.Value = XMPUtils.ConvertFromDate(binGpsStamp); } catch (XmpException) { } }
/// <summary> /// Visit all schemas to do general fixes and handle special cases. /// </summary> /// <param name="xmp"> the metadata object implementation </param> /// <exception cref="XmpException"> Thrown if the normalisation fails. </exception> 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.Root, XmpConst.NS_DC, true); // Do the special case fixes within each schema. IEnumerator it = xmp.Root.IterateChildren(); while (it.MoveNext()) { XmpNode currSchema = (XmpNode)it.Current; if (currSchema != null && XmpConst.NS_DC.Equals(currSchema.Name)) { NormalizeDcArrays(currSchema); } else if (currSchema != null && XmpConst.NS_EXIF.Equals(currSchema.Name)) { // 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 (currSchema != null && XmpConst.NS_DM.Equals(currSchema.Name)) { // 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 (currSchema != null && XmpConst.NS_XMP_RIGHTS.Equals(currSchema.Name)) { XmpNode arrayNode = XmpNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false); if (arrayNode != null) { RepairAltText(arrayNode); } } } }
/// <summary> /// Compares two nodes including its children and qualifier. </summary> /// <param name="leftNode"> an <code>XMPNode</code> </param> /// <param name="rightNode"> an <code>XMPNode</code> </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) { PropertyOptions leftForm = leftNode.Options; PropertyOptions rightForm = rightNode.Options; if (leftForm.Equals(rightForm)) { return(false); } if (leftForm.Options == 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.Struct) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.ChildrenLength != rightNode.ChildrenLength) { return(false); } for (IEnumerator it = leftNode.IterateChildren(); it.MoveNext();) { XmpNode leftField = (XmpNode)it.Current; if (leftField == null) { continue; } XmpNode 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.Array); for (IEnumerator il = leftNode.IterateChildren(); il.MoveNext();) { XmpNode leftItem = (XmpNode)il.Current; if (leftItem == null) { continue; } bool match = false; for (IEnumerator ir = rightNode.IterateChildren(); ir.MoveNext();) { XmpNode rightItem = (XmpNode)ir.Current; if (rightItem == null) { continue; } if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return(false); } } } return(true); // All of the checks passed. }
/// <seealso cref= XMPUtilsImpl#appendProperties(XMPMeta, XMPMeta, boolean, boolean, boolean) </seealso> /// <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"> </exception> private static void AppendSubtree(XmpMetaImpl destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XmpNode destNode = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.Options.Simple ? 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. PropertyOptions sourceForm = sourceNode.Options; PropertyOptions destForm = destNode.Options; if (sourceForm != destForm) { return; } if (sourceForm.Struct) { // 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 (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceField = (XmpNode)it.Current; if (sourceField == null) { continue; } AppendSubtree(destXmp, sourceField, destNode, replaceOldValues, deleteEmptyValues); if (deleteEmptyValues && !destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else if (sourceForm.ArrayAltText) { // 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 (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceItem = (XmpNode)it.Current; if (sourceItem == null) { continue; } if (!sourceItem.HasQualifier() || !XML_LANG.Equals(sourceItem.GetQualifier(1).Name)) { continue; } int 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 (!X_DEFAULT.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XmpNode destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options); sourceItem.CloneSubtree(destItem); destNode.AddChild(1, destItem); } } } } else if (sourceForm.Array) { // 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 (IEnumerator @is = sourceNode.IterateChildren(); @is.MoveNext();) { XmpNode sourceItem = (XmpNode)@is.Current; if (sourceItem == null) { continue; } bool match = false; for (IEnumerator id = destNode.IterateChildren(); id.MoveNext();) { XmpNode destItem = (XmpNode)id.Current; if (destItem == null) { continue; } if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XmpNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } }
/// <summary> /// 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> /// </summary> /// <param name="xmp"> the metadata object </param> /// <param name="dmCopyright"> the "dm:copyright"-property </param> private static void MigrateAudioCopyright(XMPMeta xmp, XmpNode dmCopyright) { try { XmpNode dcSchema = XmpNodeUtils.FindSchemaNode(((XmpMetaImpl)xmp).Root, XmpConst.NS_DC, true); string dmValue = dmCopyright.Value; const 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(XmpConst.NS_DC, "rights", "", XmpConst.X_DEFAULT, dmValue, null); } else { int xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConst.X_DEFAULT); if (xdIndex < 0) { // 2. No x-default item, create from the first item. string firstValue = dcRightsArray.GetChild(1).Value; xmp.SetLocalizedText(XmpConst.NS_DC, "rights", "", XmpConst.X_DEFAULT, firstValue, null); xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConst.X_DEFAULT); } // 3. Look for a double linefeed in the x-default value. XmpNode defaultNode = dcRightsArray.GetChild(xdIndex); string defaultValue = defaultNode.Value; 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.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) + dmValue; } } } // 4. Get rid of the xmpDM:copyright. dmCopyright.Parent.RemoveChild(dmCopyright); } catch (XmpException) { // Don't let failures (like a bad dc:rights form) stop other // cleanup. } }
/// <summary> /// 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. /// </summary> /// <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; bool strictAliasing = options.StrictAliasing; IEnumerator schemaIt = tree.UnmodifiableChildren.GetEnumerator(); while (schemaIt.MoveNext()) { XmpNode currSchema = (XmpNode)schemaIt.Current; if (currSchema == null) { continue; } if (!currSchema.HasAliases) { continue; } ArrayList currPropsToRemove = new ArrayList(); IEnumerator propertyIt = currSchema.IterateChildren(); while (propertyIt.MoveNext()) { XmpNode currProp = (XmpNode)propertyIt.Current; if (currProp == null) { continue; } if (!currProp.Alias) { continue; } currProp.Alias = false; // Find the base path, look for the base schema and root node. XMPAliasInfo info = XMPMetaFactory.SchemaRegistry.FindAlias(currProp.Name); if (info != null) { // find or create schema XmpNode baseSchema = XmpNodeUtils.FindSchemaNode(tree, info.Namespace, null, true); baseSchema.Implicit = false; XmpNode baseNode = XmpNodeUtils.FindChildNode(baseSchema, info.Prefix + info.PropName, false); if (baseNode == null) { if (info.AliasForm.Simple) { // A top-to-top alias, transplant the property. // change the alias property name to the base name string qname = info.Prefix + info.PropName; currProp.Name = qname; baseSchema.AddChild(currProp); } 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(currProp, baseNode); } currPropsToRemove.Add(currProp); } else if (info.AliasForm.Simple) { // 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); } currPropsToRemove.Add(currProp); } 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.ArrayAltText) { int xdIndex = XmpNodeUtils.LookupLanguageItem(baseNode, XmpConst.X_DEFAULT); if (xdIndex != -1) { itemNode = baseNode.GetChild(xdIndex); } } else if (baseNode.HasChildren()) { itemNode = baseNode.GetChild(1); } if (itemNode == null) { TransplantArrayItemAlias(currProp, baseNode); } else { if (strictAliasing) { CompareAliasedSubtrees(currProp, itemNode, true); } } currPropsToRemove.Add(currProp); } } } foreach (object o in currPropsToRemove) { currSchema.Children.Remove(o); } currPropsToRemove.Clear(); currSchema.HasAliases = false; } }