HasChildren() public method

public HasChildren ( ) : bool
return bool
            /// <summary>
            /// Prepares the next node to return if not already done.
            /// </summary>
            /// <seealso cref= Iterator#hasNext() </seealso>
            public override bool MoveNext()
            {
                if (_outerInstance._skipSiblings)
                {
                    return(false);
                }
                if (_childrenIterator.MoveNext())
                {
                    XmpNode child = (XmpNode)_childrenIterator.Current;
                    if (child != null)
                    {
                        _index++;
                        string path = null;
                        if (child.Options.SchemaNode)
                        {
                            _outerInstance.BaseNs = child.Name;
                        }
                        else if (child.Parent != null)
                        {
                            // for all but the root node and schema nodes
                            path = AccumulatePath(child, _parentPath, _index);
                        }

                        // report next property, skip not-leaf nodes in case options is set
                        if (!_outerInstance.Options.JustLeafnodes || !child.HasChildren())
                        {
                            ReturnProperty = CreatePropertyInfo(child, _outerInstance.BaseNs, path);
                            return(true);
                        }
                    }
                    return(MoveNext());
                }
                return(false);
            }
示例#2
0
        /// <summary>
        /// Deletes the the given node and its children from its parent.
        /// Takes care about adjusting the flags. </summary>
        /// <param name="node"> the top-most node to delete. </param>
        internal static void DeleteNode(XmpNode node)
        {
            XmpNode parent = node.Parent;

            if (node.Options.Qualifier)
            {
                // root is qualifier
                parent.RemoveQualifier(node);
            }
            else
            {
                // root is NO qualifier
                parent.RemoveChild(node);
            }

            // delete empty Schema nodes
            if (!parent.HasChildren() && parent.Options.SchemaNode)
            {
                parent.Parent.RemoveChild(parent);
            }
        }
示例#3
0
        /// <summary>
        /// See if an array is an alt-text array. If so, make sure the x-default item
        /// is first.
        /// </summary>
        /// <param name="arrayNode">
        ///            the array node to check if its an alt-text array </param>
        internal static void DetectAltText(XmpNode arrayNode)
        {
            if (arrayNode.Options.ArrayAlternate && arrayNode.HasChildren())
            {
                bool isAltText = false;
                for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();)
                {
                    XmpNode child = (XmpNode)it.Current;
                    if (child != null && child.Options != null && child.Options.HasLanguage)
                    {
                        isAltText = true;
                        break;
                    }
                }

                if (isAltText)
                {
                    arrayNode.Options.ArrayAltText = true;
                    NormalizeLangArray(arrayNode);
                }
            }
        }
示例#4
0
        /// <summary>
        /// Remove all schema children according to the flag
        /// <code>doAllProperties</code>. Empty schemas are automatically remove
        /// by <code>XMPNode</code>
        /// </summary>
        /// <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)
        {
            ArrayList currPropsToRemove = new ArrayList();

            for (IEnumerator it = schemaNode.IterateChildren(); it.MoveNext();)
            {
                XmpNode currProp = (XmpNode)it.Current;
                if (currProp == null)
                {
                    continue;
                }
                if (doAllProperties || !Utils.IsInternalProperty(schemaNode.Name, currProp.Name))
                {
                    currPropsToRemove.Add(currProp);
                }
            }
            foreach (XmpNode xmpNode in currPropsToRemove)
            {
                schemaNode.Children.Remove(xmpNode);
            }
            currPropsToRemove.Clear();
            return(!schemaNode.HasChildren());
        }
示例#5
0
        /// <summary>
        /// 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.
        /// 
        /// <blockquote>
        /// 
        /// <pre>
        /// 	&lt;ns:UnqualifiedSimpleProperty&gt;value&lt;/ns:UnqualifiedSimpleProperty&gt;
        /// 
        /// 	&lt;ns:UnqualifiedStructProperty&gt; (If no rdf:resource qualifier)
        /// 		&lt;rdf:Description&gt;
        /// 			... Fields, same forms as top level properties
        /// 		&lt;/rdf:Description&gt;
        /// 	&lt;/ns:UnqualifiedStructProperty&gt;
        /// 
        /// 	&lt;ns:ResourceStructProperty rdf:resource=&quot;URI&quot;
        /// 		... Fields as attributes
        /// 	&gt;
        /// 
        /// 	&lt;ns:UnqualifiedArrayProperty&gt;
        /// 		&lt;rdf:Bag&gt; or Seq or Alt
        /// 			... Array items as rdf:li elements, same forms as top level properties
        /// 		&lt;/rdf:Bag&gt;
        /// 	&lt;/ns:UnqualifiedArrayProperty&gt;
        /// 
        /// 	&lt;ns:QualifiedProperty&gt;
        /// 		&lt;rdf:Description&gt;
        /// 			&lt;rdf:value&gt; ... Property &quot;value&quot; following the unqualified 
        /// 				forms ... &lt;/rdf:value&gt;
        /// 			... Qualifiers looking like named struct fields
        /// 		&lt;/rdf:Description&gt;
        /// 	&lt;/ns:QualifiedProperty&gt;
        /// </pre>
        /// 
        /// </blockquote>
        /// </summary>
        /// <param name="node"> the property node </param>
        /// <param name="emitAsRdfValue"> property shall be rendered as attribute rather than tag </param>
        /// <param name="useCanonicalRdf"> use canonical form with inner description tag or 
        /// 		  the compact form with rdf:ParseType=&quot;resource&quot; attribute. </param>
        /// <param name="indent"> the current indent level </param>
        /// <exception cref="IOException"> Forwards all writer exceptions. </exception>
        /// <exception cref="XmpException"> If &quot;rdf:resource&quot; and general qualifiers are mixed. </exception>
        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.Name;
            if (emitAsRdfValue) {
                elemName = "rdf:value";
            }
            else if (XmpConst.ARRAY_ITEM_NAME.Equals(elemName)) {
                elemName = "rdf:li";
            }

            WriteIndent(indent);
            Write('<');
            Write(elemName);

            bool hasGeneralQualifiers = false;
            bool hasRdfResourceQual = false;

            for (IEnumerator it = node.IterateQualifier(); it.MoveNext();) {
                XmpNode qualifier = (XmpNode) it.Current;
                if (qualifier != null) {
                    if (!RDF_ATTR_QUALIFIER.Contains(qualifier.Name)) {
                        hasGeneralQualifiers = true;
                    }
                    else {
                        hasRdfResourceQual = "rdf:resource".Equals(qualifier.Name);
                        if (!emitAsRdfValue) {
                            Write(' ');
                            Write(qualifier.Name);
                            Write("=\"");
                            AppendNodeValue(qualifier.Value, 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", XmpError.BADRDF);
                }

                // Change serialization to canonical format with inner rdf:Description-tag
                // depending on option
                if (useCanonicalRdf) {
                    Write(">");
                    WriteNewline();

                    indent++;
                    WriteIndent(indent);
                    Write(RDF_STRUCT_START);
                    Write(">");
                }
                else {
                    Write(" rdf:parseType=\"Resource\">");
                }
                WriteNewline();

                SerializeCanonicalRdfProperty(node, useCanonicalRdf, true, indent + 1);

                for (IEnumerator it = node.IterateQualifier(); it.MoveNext();) {
                    XmpNode qualifier = (XmpNode) it.Current;
                    if (qualifier != null && !RDF_ATTR_QUALIFIER.Contains(qualifier.Name)) {
                        SerializeCanonicalRdfProperty(qualifier, useCanonicalRdf, false, indent + 1);
                    }
                }

                if (useCanonicalRdf) {
                    WriteIndent(indent);
                    Write(RDF_STRUCT_END);
                    WriteNewline();
                    indent--;
                }
            }
            else {
                // This node has no general qualifiers. Emit using an unqualified form.

                if (!node.Options.CompositeProperty) {
                    // This is a simple property.

                    if (node.Options.Uri) {
                        Write(" rdf:resource=\"");
                        AppendNodeValue(node.Value, true);
                        Write("\"/>");
                        WriteNewline();
                        emitEndTag = false;
                    }
                    else if (node.Value == null || "".Equals(node.Value)) {
                        Write("/>");
                        WriteNewline();
                        emitEndTag = false;
                    }
                    else {
                        Write('>');
                        AppendNodeValue(node.Value, false);
                        indentEndTag = false;
                    }
                }
                else if (node.Options.Array) {
                    // This is an array.
                    Write('>');
                    WriteNewline();
                    EmitRdfArrayTag(node, true, indent + 1);
                    if (node.Options.ArrayAltText) {
                        XmpNodeUtils.NormalizeLangArray(node);
                    }
                    for (IEnumerator it = node.IterateChildren(); it.MoveNext();) {
                        XmpNode child = (XmpNode) it.Current;
                        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(RDF_EMPTY_STRUCT);
                        }
                        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(RDF_STRUCT_START);
                            Write(">");
                        }
                        else {
                            Write(" rdf:parseType=\"Resource\">");
                        }
                        WriteNewline();

                        for (IEnumerator it = node.IterateChildren(); it.MoveNext();) {
                            XmpNode child = (XmpNode) it.Current;
                            SerializeCanonicalRdfProperty(child, useCanonicalRdf, false, indent + 1);
                        }

                        if (useCanonicalRdf) {
                            WriteIndent(indent);
                            Write(RDF_STRUCT_END);
                            WriteNewline();
                            indent--;
                        }
                    }
                }
                else {
                    // This is a struct with an rdf:resource attribute, use the
                    // "empty property element" form.
                    for (IEnumerator it = node.IterateChildren(); it.MoveNext();) {
                        XmpNode child = (XmpNode) it.Current;
                        if (child != null) {
                            if (!canBeRDFAttrProp(child)) {
                                throw new XmpException("Can't mix rdf:resource and complex fields",
                                                       XmpError.BADRDF);
                            }
                            WriteNewline();
                            WriteIndent(indent + 1);
                            Write(' ');
                            Write(child.Name);
                            Write("=\"");
                            AppendNodeValue(child.Value, true);
                            Write('"');
                        }
                    }
                    Write("/>");
                    WriteNewline();
                    emitEndTag = false;
                }
            }

            // Emit the property element end tag.
            if (emitEndTag) {
                if (indentEndTag) {
                    WriteIndent(indent);
                }
                Write("</");
                Write(elemName);
                Write('>');
                WriteNewline();
            }
        }
示例#6
0
        /// <summary>
        /// Serializes a struct property.
        /// </summary>
        /// <param name="node"> an XMPNode </param>
        /// <param name="indent"> the current indent level </param>
        /// <param name="hasRdfResourceQual"> Flag if the element has resource qualifier </param>
        /// <returns> Returns true if an end flag shall be emitted. </returns>
        /// <exception cref="IOException"> Forwards the writer exceptions. </exception>
        /// <exception cref="XmpException"> If qualifier and element fields are mixed. </exception>
        private bool SerializeCompactRdfStructProp(XmpNode node, int indent, bool hasRdfResourceQual) {
            // This must be a struct.
            bool hasAttrFields = false;
            bool hasElemFields = false;
            bool emitEndTag = true;

            for (IEnumerator ic = node.IterateChildren(); ic.MoveNext();) {
                XmpNode field = (XmpNode) ic.Current;
                if (field == null)
                    continue;
                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", XmpError.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(RDF_STRUCT_START);
                SerializeCompactRdfAttrProps(node, indent + 2);
                Write(">");
                WriteNewline();
                SerializeCompactRdfElementProps(node, indent + 1);
                WriteIndent(indent + 1);
                Write(RDF_STRUCT_END);
                WriteNewline();
            }
            return emitEndTag;
        }
示例#7
0
        /// <summary>
        /// Writes the array start and end tags.
        /// </summary>
        /// <param name="arrayNode"> an array node </param>
        /// <param name="isStartTag"> flag if its the start or end tag </param>
        /// <param name="indent"> the current indent level </param>
        /// <exception cref="IOException"> forwards writer exceptions </exception>
        private void EmitRdfArrayTag(XmpNode arrayNode, bool isStartTag, int indent) {
            if (isStartTag || arrayNode.HasChildren()) {
                WriteIndent(indent);
                Write(isStartTag ? "<rdf:" : "</rdf:");

                if (arrayNode.Options.ArrayAlternate) {
                    Write("Alt");
                }
                else if (arrayNode.Options.ArrayOrdered) {
                    Write("Seq");
                }
                else {
                    Write("Bag");
                }

                if (isStartTag && !arrayNode.HasChildren()) {
                    Write("/>");
                }
                else {
                    Write(">");
                }

                WriteNewline();
            }
        }
示例#8
0
        /// <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);
                        }
                    }
                }
            }
        }
示例#9
0
 /// <summary>
 /// Remove all schema children according to the flag
 /// <code>doAllProperties</code>. Empty schemas are automatically remove
 /// by <code>XMPNode</code>
 /// </summary>
 /// <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) {
     ArrayList currPropsToRemove = new ArrayList();
     for (IEnumerator it = schemaNode.IterateChildren(); it.MoveNext();) {
         XmpNode currProp = (XmpNode) it.Current;
         if (currProp == null)
             continue;
         if (doAllProperties || !Utils.IsInternalProperty(schemaNode.Name, currProp.Name)) {
             currPropsToRemove.Add(currProp);
         }
     }
     foreach (XmpNode xmpNode in currPropsToRemove) {
         schemaNode.Children.Remove(xmpNode);
     }
     currPropsToRemove.Clear();
     return !schemaNode.HasChildren();
 }
示例#10
0
        /// <summary>
        /// <ol>
        /// <li>Look for an exact match with the specific language.
        /// <li>If a generic language is given, look for partial matches.
        /// <li>Look for an "x-default"-item.
        /// <li>Choose the first item.
        /// </ol>
        /// </summary>
        /// <param name="arrayNode">
        ///            the alt text array node </param>
        /// <param name="genericLang">
        ///            the generic language </param>
        /// <param name="specificLang">
        ///            the specific language </param>
        /// <returns> Returns the kind of match as an Integer and the found node in an
        ///         array.
        /// </returns>
        /// <exception cref="XmpException"> </exception>
        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.Options.ArrayAltText) {
                throw new XmpException("Localized text array is not alt-text", XmpError.BADXPATH);
            }
            if (!arrayNode.HasChildren()) {
                return new object[] {CLT_NO_VALUES, null};
            }

            int foundGenericMatches = 0;
            XmpNode resultNode = null;
            XmpNode xDefault = null;

            // Look for the first partial match with the generic language.
            for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) {
                XmpNode currItem = (XmpNode) it.Current;

                // perform some checks on the current item
                if (currItem == null || currItem.Options == null || currItem.Options.CompositeProperty) {
                    throw new XmpException("Alt-text array item is not simple", XmpError.BADXPATH);
                }
                if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name)) {
                    throw new XmpException("Alt-text array item has no language qualifier", XmpError.BADXPATH);
                }

                string currLang = currItem.GetQualifier(1).Value;

                // Look for an exact match with the specific language.
                if (specificLang.Equals(currLang)) {
                    return new object[] {CLT_SPECIFIC_MATCH, currItem};
                }
                if (genericLang != null && currLang.StartsWith(genericLang)) {
                    if (resultNode == null) {
                        resultNode = currItem;
                    }
                    // ! Don't return/break, need to look for other matches.
                    foundGenericMatches++;
                }
                else if (X_DEFAULT.Equals(currLang)) {
                    xDefault = currItem;
                }
            }

            // evaluate loop
            if (foundGenericMatches == 1) {
                return new object[] {CLT_SINGLE_GENERIC, resultNode};
            }
            if (foundGenericMatches > 1) {
                return new object[] {CLT_MULTIPLE_GENERIC, resultNode};
            }
            if (xDefault != null) {
                return new object[] {CLT_XDEFAULT, xDefault};
            }
            {
                // Everything failed, choose the first item.
                return new object[] {CLT_FIRST_ITEM, arrayNode.GetChild(1)};
            }
        }
示例#11
0
        /// <seealso cref= XMPUtils#removeProperties(XMPMeta, String, String, boolean, boolean)
        /// </seealso>
        /// <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);
            XmpMetaImpl xmpImpl = (XmpMetaImpl)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", XmpError.BADPARAM);
                }

                XmpPath expPath = XmpPathParser.ExpandXPath(schemaNs, propName);

                XmpNode propNode = XmpNodeUtils.FindNode(xmpImpl.Root, expPath, false, null);
                if (propNode != null)
                {
                    if (doAllProperties ||
                        !Utils.IsInternalProperty(expPath.GetSegment((int)XmpPath.STEP_SCHEMA).Name,
                                                  expPath.GetSegment((int)XmpPath.STEP_ROOT_PROP).Name))
                    {
                        XmpNode parent = propNode.Parent;
                        parent.RemoveChild(propNode);
                        if (parent.Options.SchemaNode && !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;
                XmpNode schemaNode = XmpNodeUtils.FindSchemaNode(xmpImpl.Root, schemaNs, false);
                if (schemaNode != null)
                {
                    if (RemoveSchemaChildren(schemaNode, doAllProperties))
                    {
                        xmpImpl.Root.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.

                    IXmpAliasInfo[] aliases = XmpMetaFactory.SchemaRegistry.FindAliases(schemaNs);
                    for (int i = 0; i < aliases.Length; i++)
                    {
                        IXmpAliasInfo info       = aliases[i];
                        XmpPath       path       = XmpPathParser.ExpandXPath(info.Namespace, info.PropName);
                        XmpNode       actualProp = XmpNodeUtils.FindNode(xmpImpl.Root, path, false, null);
                        if (actualProp != null)
                        {
                            XmpNode parent = actualProp.Parent;
                            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.
                ArrayList schemasToRemove = new ArrayList();
                for (IEnumerator it = xmpImpl.Root.IterateChildren(); it.MoveNext();)
                {
                    XmpNode schema = (XmpNode)it.Current;
                    if (schema == null)
                    {
                        continue;
                    }
                    if (RemoveSchemaChildren(schema, doAllProperties))
                    {
                        schemasToRemove.Add(schema);
                    }
                }
                foreach (XmpNode xmpNode in schemasToRemove)
                {
                    xmpImpl.Root.Children.Remove(xmpNode);
                }
                schemasToRemove.Clear();
            }
        }
示例#12
0
        /// <summary>
        /// <ol>
        /// <li>Look for an exact match with the specific language.
        /// <li>If a generic language is given, look for partial matches.
        /// <li>Look for an "x-default"-item.
        /// <li>Choose the first item.
        /// </ol>
        /// </summary>
        /// <param name="arrayNode">
        ///            the alt text array node </param>
        /// <param name="genericLang">
        ///            the generic language </param>
        /// <param name="specificLang">
        ///            the specific language </param>
        /// <returns> Returns the kind of match as an Integer and the found node in an
        ///         array.
        /// </returns>
        /// <exception cref="XmpException"> </exception>
        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.Options.ArrayAltText)
            {
                throw new XmpException("Localized text array is not alt-text", XmpError.BADXPATH);
            }
            if (!arrayNode.HasChildren())
            {
                return(new object[] { CLT_NO_VALUES, null });
            }

            int     foundGenericMatches = 0;
            XmpNode resultNode          = null;
            XmpNode xDefault            = null;

            // Look for the first partial match with the generic language.
            for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();)
            {
                XmpNode currItem = (XmpNode)it.Current;

                // perform some checks on the current item
                if (currItem == null || currItem.Options == null || currItem.Options.CompositeProperty)
                {
                    throw new XmpException("Alt-text array item is not simple", XmpError.BADXPATH);
                }
                if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name))
                {
                    throw new XmpException("Alt-text array item has no language qualifier", XmpError.BADXPATH);
                }

                string currLang = currItem.GetQualifier(1).Value;

                // Look for an exact match with the specific language.
                if (specificLang.Equals(currLang))
                {
                    return(new object[] { CLT_SPECIFIC_MATCH, currItem });
                }
                if (genericLang != null && currLang.StartsWith(genericLang))
                {
                    if (resultNode == null)
                    {
                        resultNode = currItem;
                    }
                    // ! Don't return/break, need to look for other matches.
                    foundGenericMatches++;
                }
                else if (X_DEFAULT.Equals(currLang))
                {
                    xDefault = currItem;
                }
            }

            // evaluate loop
            if (foundGenericMatches == 1)
            {
                return(new object[] { CLT_SINGLE_GENERIC, resultNode });
            }
            if (foundGenericMatches > 1)
            {
                return(new object[] { CLT_MULTIPLE_GENERIC, resultNode });
            }
            if (xDefault != null)
            {
                return(new object[] { CLT_XDEFAULT, xDefault });
            }
            {
                // Everything failed, choose the first item.
                return(new object[] { CLT_FIRST_ITEM, arrayNode.GetChild(1) });
            }
        }
示例#13
0
        /// <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);
                        }
                    }
                }
            }
        }
示例#14
0
        /// <seealso cref= XMPMeta#setLocalizedText(String, String, String, String, String,
        ///      PropertyOptions) </seealso>
        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.ARRAY_ORDERED |
                                                                          PropertyOptions.ARRAY_ALTERNATE |
                                                                          PropertyOptions.ARRAY_ALT_TEXT));

            if (arrayNode == null)
            {
                throw new XmpException("Failed to find or create array node", XmpError.BADXPATH);
            }
            if (!arrayNode.Options.ArrayAltText)
            {
                if (!arrayNode.HasChildren() && arrayNode.Options.ArrayAlternate)
                {
                    arrayNode.Options.ArrayAltText = true;
                }
                else
                {
                    throw new XmpException("Specified property is no alt-text array", XmpError.BADXPATH);
                }
            }

            // Make sure the x-default item, if any, is first.
            bool    haveXDefault = false;
            XmpNode xdItem       = null;

            foreach (XmpNode currItem in arrayNode.Children)
            {
                if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name))
                {
                    throw new XmpException("Language qualifier must be first", XmpError.BADXPATH);
                }
                if (X_DEFAULT.Equals(currItem.GetQualifier(1).Value))
                {
                    xdItem       = currItem;
                    haveXDefault = true;
                    break;
                }
            }

            // Moves x-default to the beginning of the array
            if (xdItem != null && arrayNode.ChildrenLength > 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)((int?)result[0]);
            XmpNode  itemNode = (XmpNode)result[1];

            bool specificXDefault = X_DEFAULT.Equals(specificLang);

            switch (match)
            {
            case XmpNodeUtils.CLT_NO_VALUES:

                // Create the array items for the specificLang and x-default, with
                // x-default first.
                XmpNodeUtils.AppendLangItem(arrayNode, X_DEFAULT, itemValue);
                haveXDefault = true;
                if (!specificXDefault)
                {
                    XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                }
                break;

            case XmpNodeUtils.CLT_SPECIFIC_MATCH:

                if (!specificXDefault)
                {
                    // Update the specific item, update x-default if it matches the
                    // old value.
                    if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.Value.Equals(itemNode.Value))
                    {
                        xdItem.Value = itemValue;
                    }
                    // ! Do this after the x-default check!
                    itemNode.Value = itemValue;
                }
                else
                {
                    // Update all items whose values match the old x-default value.
                    Debug.Assert(haveXDefault && xdItem == itemNode);
                    foreach (XmpNode currItem in arrayNode.Children)
                    {
                        if (currItem == xdItem || !currItem.Value.Equals(xdItem != null ? xdItem.Value : null))
                        {
                            continue;
                        }
                        currItem.Value = itemValue;
                    }
                    // And finally do the x-default item.
                    if (xdItem != null)
                    {
                        xdItem.Value = itemValue;
                    }
                }
                break;

            case XmpNodeUtils.CLT_SINGLE_GENERIC:

                // Update the generic item, update x-default if it matches the old
                // value.
                if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.Value.Equals(itemNode.Value))
                {
                    xdItem.Value = itemValue;
                }
                itemNode.Value = itemValue;     // ! Do this after
                // the x-default
                // check!
                break;

            case XmpNodeUtils.CLT_MULTIPLE_GENERIC:

                // Create the specific language, ignore x-default.
                XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                if (specificXDefault)
                {
                    haveXDefault = true;
                }
                break;

            case XmpNodeUtils.CLT_XDEFAULT:

                // Create the specific language, update x-default if it was the only
                // item.
                if (xdItem != null && arrayNode.ChildrenLength == 1)
                {
                    xdItem.Value = itemValue;
                }
                XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                break;

            case XmpNodeUtils.CLT_FIRST_ITEM:

                // 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",
                                       XmpError.INTERNALFAILURE);
            }

            // Add an x-default at the front if needed.
            if (!haveXDefault && arrayNode.ChildrenLength == 1)
            {
                XmpNodeUtils.AppendLangItem(arrayNode, X_DEFAULT, itemValue);
            }
        }
示例#15
0
        /// <summary>
        /// See if an array is an alt-text array. If so, make sure the x-default item
        /// is first.
        /// </summary>
        /// <param name="arrayNode">
        ///            the array node to check if its an alt-text array </param>
        internal static void DetectAltText(XmpNode arrayNode) {
            if (arrayNode.Options.ArrayAlternate && arrayNode.HasChildren()) {
                bool isAltText = false;
                for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) {
                    XmpNode child = (XmpNode) it.Current;
                    if (child != null && child.Options != null && child.Options.HasLanguage) {
                        isAltText = true;
                        break;
                    }
                }

                if (isAltText) {
                    arrayNode.Options.ArrayAltText = true;
                    NormalizeLangArray(arrayNode);
                }
            }
        }
示例#16
0
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="xmpParent"> the parent xmp node </param>
        /// <exception cref="XmpException"> thown on parsing errors </exception>
        private static void FixupQualifiedNode(XmpNode xmpParent) {
            Debug.Assert(xmpParent.Options.Struct && xmpParent.HasChildren());

            XmpNode valueNode = xmpParent.GetChild(1);
            Debug.Assert("rdf:value".Equals(valueNode.Name));

            // 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.Options.HasLanguage) {
                if (xmpParent.Options.HasLanguage) {
                    throw new XmpException("Redundant xml:lang for rdf:value element", XmpError.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.QualifierLength; 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 = 2; i <= xmpParent.ChildrenLength; i++) {
                XmpNode qualifier = xmpParent.GetChild(i);
                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.Options.Struct || xmpParent.HasValueChild);

            xmpParent.HasValueChild = false;
            xmpParent.Options.Struct = false;
            xmpParent.Options.MergeWith(valueNode.Options);
            xmpParent.Value = valueNode.Value;

            xmpParent.RemoveChildren();
            for (IEnumerator it = valueNode.IterateChildren(); it.MoveNext();) {
                XmpNode child = (XmpNode) it.Current;
                xmpParent.AddChild(child);
            }
        }
示例#17
0
        /// <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.
            }
        }