/// <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); } } }
/// <param name="xmp">The XMP object containing the array to be catenated.</param> /// <param name="schemaNs"> /// The schema namespace URI for the array. Must not be null or /// the empty string. /// </param> /// <param name="arrayName"> /// 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. /// </param> /// <param name="separator"> /// The string to be used to separate the items in the catenated /// string. Defaults to "; ", ASCII semicolon and space /// (U+003B, U+0020). /// </param> /// <param name="quotes"> /// The characters to be used as quotes around array items that /// contain a separator. Defaults to '"' /// </param> /// <param name="allowCommas">Option flag to control the catenation.</param> /// <returns>Returns the string containing the catenated array items.</returns> /// <exception cref="XmpException">Forwards the Exceptions from the metadata processing</exception> public static string CatenateArrayItems(IXmpMeta xmp, string schemaNs, string arrayName, string separator, string quotes, bool allowCommas) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); ParameterAsserts.AssertImplementation(xmp); if (string.IsNullOrEmpty(separator)) { separator = "; "; } if (string.IsNullOrEmpty(quotes)) { quotes = "\""; } var xmpImpl = (XmpMeta)xmp; // Return an empty result if the array does not exist, // hurl if it isn't the right form. var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName); var arrayNode = XmpNodeUtils.FindNode(xmpImpl.GetRoot(), arrayPath, false, null); if (arrayNode == null) { return(string.Empty); } if (!arrayNode.Options.IsArray || arrayNode.Options.IsArrayAlternate) { throw new XmpException("Named property must be non-alternate array", XmpErrorCode.BadParam); } // Make sure the separator is OK. CheckSeparator(separator); // Make sure the open and close quotes are a legitimate pair. var openQuote = quotes[0]; var closeQuote = CheckQuotes(quotes, openQuote); // Build the result, quoting the array items, adding separators. // Hurl if any item isn't simple. var catenatedString = new StringBuilder(); for (var it = arrayNode.IterateChildren(); it.HasNext();) { var currItem = (XmpNode)it.Next(); if (currItem.Options.IsCompositeProperty) { throw new XmpException("Array items must be simple", XmpErrorCode.BadParam); } var str = ApplyQuotes(currItem.Value, openQuote, closeQuote, allowCommas); catenatedString.Append(str); if (it.HasNext()) { catenatedString.Append(separator); } } return(catenatedString.ToString()); }
/// <param name="xmp">The XMP object containing the properties to be removed.</param> /// <param name="schemaNs"> /// Optional schema namespace URI for the properties to be /// removed. /// </param> /// <param name="propName">Optional path expression for the property to be removed.</param> /// <param name="doAllProperties"> /// Option flag to control the deletion: do internal properties in /// addition to external properties. /// </param> /// <param name="includeAliases"> /// Option flag to control the deletion: Include aliases in the /// "named schema" case above. /// </param> /// <exception cref="XmpException">If metadata processing fails</exception> public static void RemoveProperties(IXmpMeta xmp, string schemaNs, string propName, bool doAllProperties, bool includeAliases) { ParameterAsserts.AssertImplementation(xmp); var xmpImpl = (XmpMeta)xmp; if (!string.IsNullOrEmpty(propName)) { // 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 (string.IsNullOrEmpty(schemaNs)) { throw new XmpException("Property name requires schema namespace", XmpErrorCode.BadParam); } var expPath = XmpPathParser.ExpandXPath(schemaNs, propName); var propNode = XmpNodeUtils.FindNode(xmpImpl.GetRoot(), expPath, false, null); if (propNode != null) { if (doAllProperties || !Utils.IsInternalProperty(expPath.GetSegment(XmpPath.StepSchema).Name, expPath.GetSegment(XmpPath.StepRootProp).Name)) { var parent = propNode.Parent; parent.RemoveChild(propNode); if (parent.Options.IsSchemaNode && !parent.HasChildren) { // remove empty schema node parent.Parent.RemoveChild(parent); } } } } else { if (!string.IsNullOrEmpty(schemaNs)) { // Remove all properties from the named schema. Optionally include // aliases, in which case // there might not be an actual schema node. // XMP_NodePtrPos schemaPos; var schemaNode = XmpNodeUtils.FindSchemaNode(xmpImpl.GetRoot(), schemaNs, false); if (schemaNode != null && 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. foreach (var info in XmpMetaFactory.SchemaRegistry.FindAliases(schemaNs)) { var path = XmpPathParser.ExpandXPath(info.Namespace, info.PropName); var actualProp = XmpNodeUtils.FindNode(xmpImpl.GetRoot(), path, false, null); if (actualProp != null) { actualProp.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 (var it = xmpImpl.GetRoot().IterateChildren(); it.HasNext();) { var schema = (XmpNode)it.Next(); if (RemoveSchemaChildren(schema, doAllProperties)) { it.Remove(); } } } } }
/// <summary> /// See <see cref="XmpCore.XmpUtils.SeparateArrayItems(IXmpMeta, string, string, string, PropertyOptions, bool)"/>. /// </summary> /// <param name="xmp">The XMP object containing the array to be updated.</param> /// <param name="schemaNs"> /// The schema namespace URI for the array. Must not be null or the empty string. /// </param> /// <param name="arrayName"> /// 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. /// </param> /// <param name="catedStr">The string to be separated into the array items.</param> /// <param name="arrayOptions">Option flags to control the separation.</param> /// <param name="preserveCommas">Flag if commas shall be preserved</param> /// <exception cref="XmpException">Forwards the Exceptions from the metadata processing</exception> public static void SeparateArrayItems(IXmpMeta 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", XmpErrorCode.BadParam); } ParameterAsserts.AssertImplementation(xmp); var xmpImpl = (XmpMeta)xmp; // Keep a zero value, has special meaning below. var arrayNode = SeparateFindCreateArray(schemaNs, arrayName, arrayOptions, xmpImpl); // Extract the item values one at a time, until the whole input string is done. var charKind = UnicodeKind.Normal; var ch = (char)0; var itemEnd = 0; var 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. int itemStart; for (itemStart = itemEnd; itemStart < endPos; itemStart++) { ch = catedStr[itemStart]; charKind = ClassifyCharacter(ch); if (charKind == UnicodeKind.Normal || charKind == UnicodeKind.Quote) { break; } } if (itemStart >= endPos) { break; } string itemValue; if (charKind != UnicodeKind.Quote) { // 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 == UnicodeKind.Normal || charKind == UnicodeKind.Quote || (charKind == UnicodeKind.Comma && preserveCommas)) { continue; } if (charKind != UnicodeKind.Space) { break; } if ((itemEnd + 1) < endPos) { ch = catedStr[itemEnd + 1]; var nextKind = ClassifyCharacter(ch); if (nextKind == UnicodeKind.Normal || nextKind == UnicodeKind.Quote || (nextKind == UnicodeKind.Comma && preserveCommas)) { continue; } } // Anything left? break; } // Have multiple spaces, or a space followed by a // separator. itemValue = catedStr.Substring(itemStart, itemEnd - itemStart); } else { // Accumulate quoted values into a local string, undoubling // internal quotes that // match the surrounding quotes. Do not undouble "unmatching" // quotes. var openQuote = ch; var 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 != UnicodeKind.Quote || !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. char nextChar; if ((itemEnd + 1) < endPos) { nextChar = catedStr[itemEnd + 1]; } else { 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 (!IsClosingQuote(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. var foundIndex = -1; for (var oldChild = 1; oldChild <= arrayNode.GetChildrenLength(); oldChild++) { if (itemValue.Equals(arrayNode.GetChild(oldChild).Value)) { foundIndex = oldChild; break; } } if (foundIndex < 0) { arrayNode.AddChild(new XmpNode(XmpConstants.ArrayItemName, itemValue, null)); } } }