Exemple #1
0
 /// <summary>Constructor for an empty metadata object.</summary>
 public XmpMeta()
 {
     // create root node
     _tree = new XmpNode(null, null, null);
 }
Exemple #2
0
            public XmpProperty(XmpNode itemNode)
            {
                _node = itemNode;

                _proptype = XmpPropertyType.item;
            }
Exemple #3
0
 /// <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;
Exemple #4
0
        /// <exception cref="XmpException"/>
        public 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);
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, altTextName);
            // Find the array node and set the options if it was just created.
            var arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, true, new PropertyOptions(PropertyOptions.ArrayFlag | PropertyOptions.ArrayOrderedFlag | PropertyOptions.ArrayAlternateFlag | PropertyOptions.ArrayAltTextFlag));

            if (arrayNode == null)
            {
                throw new XmpException("Failed to find or create array node", XmpErrorCode.BadXPath);
            }
            if (!arrayNode.Options.IsArrayAltText)
            {
                if (!arrayNode.HasChildren && arrayNode.Options.IsArrayAlternate)
                {
                    arrayNode.Options.IsArrayAltText = true;
                }
                else
                {
                    throw new XmpException("Specified property is no alt-text array", XmpErrorCode.BadXPath);
                }
            }
            // Make sure the x-default item, if any, is first.
            var     haveXDefault = false;
            XmpNode xdItem       = null;

            for (var it = arrayNode.IterateChildren(); it.HasNext();)
            {
                var currItem = (XmpNode)it.Next();
                if (!currItem.HasQualifier || currItem.GetQualifier(1).Name != XmpConstants.XmlLang)
                {
                    throw new XmpException("Language qualifier must be first", XmpErrorCode.BadXPath);
                }

                if (currItem.GetQualifier(1).Value == XmpConstants.XDefault)
                {
                    xdItem       = currItem;
                    haveXDefault = true;
                    break;
                }
            }
            // Moves x-default to the beginning of the array
            if (xdItem != null && arrayNode.GetChildrenLength() > 1)
            {
                arrayNode.RemoveChild(xdItem);
                arrayNode.AddChild(1, xdItem);
            }
            // Find the appropriate item.
            // chooseLocalizedText will make sure the array is a language
            // alternative.
            var result           = XmpNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang);
            var match            = (int)result[0];
            var itemNode         = (XmpNode)result[1];
            var specificXDefault = specificLang == XmpConstants.XDefault;

            switch (match)
            {
            case XmpNodeUtils.CltNoValues:
            {
                // Create the array items for the specificLang and x-default, with
                // x-default first.
                XmpNodeUtils.AppendLangItem(arrayNode, XmpConstants.XDefault, itemValue);
                haveXDefault = true;
                if (!specificXDefault)
                {
                    XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                }
                break;
            }

            case XmpNodeUtils.CltSpecificMatch:
            {
                if (!specificXDefault)
                {
                    // Update the specific item, update x-default if it matches the
                    // old value.
                    if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.Value == 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);
                    for (var it1 = arrayNode.IterateChildren(); it1.HasNext();)
                    {
                        var currItem = (XmpNode)it1.Next();
                        if (currItem == xdItem || currItem.Value != xdItem?.Value)
                        {
                            continue;
                        }

                        currItem.Value = itemValue;
                    }
                    // And finally do the x-default item.
                    if (xdItem != null)
                    {
                        xdItem.Value = itemValue;
                    }
                }
                break;
            }

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

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

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

            case XmpNodeUtils.CltFirstItem:
            {
                // Create the specific language, don't add an x-default item.
                XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                if (specificXDefault)
                {
                    haveXDefault = true;
                }
                break;
            }

            default:
            {
                // does not happen under normal circumstances
                throw new XmpException("Unexpected result from ChooseLocalizedText", XmpErrorCode.InternalFailure);
            }
            }
            // Add an x-default at the front if needed.
            if (!haveXDefault && arrayNode.GetChildrenLength() == 1)
            {
                XmpNodeUtils.AppendLangItem(arrayNode, XmpConstants.XDefault, itemValue);
            }
        }
Exemple #5
0
 /// <summary>
 /// A node can be serialized as RDF-Attribute, if it meets the following conditions:
 /// <list type="bullet">
 /// <item>is not array item</item>
 /// <item>don't has qualifier</item>
 /// <item>is no URI</item>
 /// <item>is no composite property</item>
 /// </list>
 /// </summary>
 /// <param name="node">an XMPNode</param>
 /// <returns>Returns true if the node serialized as RDF-Attribute</returns>
 private static bool CanBeRdfAttrProp(XmpNode node)
 {
     // FfF: other possibilities than []? if ( propNode->name[0] == '[' ) return false;
     return(!node.HasQualifier && !node.Options.IsUri && !node.Options.IsCompositeProperty && node.Name != XmpConstants.ArrayItemName);
 }
 /// <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);
 }
Exemple #7
0
        /// <summary>
        /// </summary>
        /// <remarks>
        /// <list>
        /// <item>Look for an exact match with the specific language.</item>
        /// <item>If a generic language is given, look for partial matches.</item>
        /// <item>Look for an "x-default"-item.</item>
        /// <item>Choose the first item.</item>
        /// </list>
        /// </remarks>
        /// <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" />
        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.IsArrayAltText)
            {
                throw new XmpException("Localized text array is not alt-text", XmpErrorCode.BadXPath);
            }

            if (!arrayNode.HasChildren)
            {
                return new object[] { CltNoValues, null }
            }
            ;

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

            // Look for the first partial match with the generic language.
            for (var it = arrayNode.IterateChildren(); it.HasNext();)
            {
                var currItem = (XmpNode)it.Next();

                // perform some checks on the current item
                if (currItem.Options.IsCompositeProperty)
                {
                    throw new XmpException("Alt-text array item is not simple", XmpErrorCode.BadXPath);
                }

                if (!currItem.HasQualifier || currItem.GetQualifier(1).Name != XmpConstants.XmlLang)
                {
                    throw new XmpException("Alt-text array item has no language qualifier", XmpErrorCode.BadXPath);
                }

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

                // Look for an exact match with the specific language.
                if (currLang == specificLang)
                {
                    return new object[] { CltSpecificMatch, 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 (currLang == XmpConstants.XDefault)
                {
                    xDefault = currItem;
                }
            }

            // evaluate loop
            if (foundGenericMatches == 1)
            {
                return new object[] { CltSingleGeneric, resultNode }
            }
            ;

            if (foundGenericMatches > 1)
            {
                return new object[] { CltMultipleGeneric, resultNode }
            }
            ;

            if (xDefault != null)
            {
                return new object[] { CltXDefault, xDefault }
            }
            ;

            // Everything failed, choose the first item.
            return(new object[] { CltFirstItem, arrayNode.GetChild(1) });
        }
        /// <summary>Recursively handles the "value" for a node.</summary>
        /// <remarks>
        /// 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.
        /// <code>
        /// &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;
        /// </code>
        /// </remarks>
        /// <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="System.IO.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)
        {
            var emitEndTag   = true;
            var indentEndTag = true;

            // Determine the XML element name. Open the start tag with the name and
            // attribute qualifiers.
            var elemName = node.Name;

            if (emitAsRdfValue)
            {
                elemName = "rdf:value";
            }
            else if (elemName == XmpConstants.ArrayItemName)
            {
                elemName = XmpConstants.RdfLi;
            }

            WriteIndent(indent);
            Write('<');
            Write(elemName);
            var hasGeneralQualifiers = false;
            var hasRdfResourceQual   = false;

            for (var it = node.IterateQualifier(); it.HasNext();)
            {
                var qualifier = (XmpNode)it.Next();
                if (!RdfAttrQualifier.Contains(qualifier.Name))
                {
                    hasGeneralQualifiers = true;
                }
                else
                {
                    hasRdfResourceQual = qualifier.Name == "rdf:resource";
                    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", XmpErrorCode.BadRdf);
                }

                // Change serialization to canonical format with inner rdf:Description-tag
                // depending on option
                if (useCanonicalRdf)
                {
                    Write(">");
                    WriteNewline();
                    indent++;
                    WriteIndent(indent);
                    Write(RdfStructStart);
                    Write(">");
                }
                else
                {
                    Write(" rdf:parseType=\"Resource\">");
                }

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

                for (var it = node.IterateQualifier(); it.HasNext();)
                {
                    var qualifier = (XmpNode)it.Next();
                    if (!RdfAttrQualifier.Contains(qualifier.Name))
                    {
                        SerializeCanonicalRdfProperty(qualifier, useCanonicalRdf, false, indent + 1);
                    }
                }

                if (useCanonicalRdf)
                {
                    WriteIndent(indent);
                    Write(RdfStructEnd);
                    WriteNewline();
                    indent--;
                }
            }
            else
            {
                // This node has no general qualifiers. Emit using an unqualified form.
                if (!node.Options.IsCompositeProperty)
                {
                    // This is a simple property.
                    if (node.Options.IsUri)
                    {
                        Write(" rdf:resource=\"");
                        AppendNodeValue(node.Value, true);
                        Write("\"/>");
                        WriteNewline();
                        emitEndTag = false;
                    }
                    else if (string.IsNullOrEmpty(node.Value))
                    {
                        Write("/>");
                        WriteNewline();
                        emitEndTag = false;
                    }
                    else
                    {
                        Write('>');
                        AppendNodeValue(node.Value, false);
                        indentEndTag = false;
                    }
                }
                else
                {
                    if (node.Options.IsArray)
                    {
                        // This is an array.
                        Write('>');
                        WriteNewline();
                        EmitRdfArrayTag(node, true, indent + 1);

                        if (node.Options.IsArrayAltText)
                        {
                            XmpNodeUtils.NormalizeLangArray(node);
                        }

                        for (var it1 = node.IterateChildren(); it1.HasNext();)
                        {
                            var child = (XmpNode)it1.Next();
                            SerializeCanonicalRdfProperty(child, useCanonicalRdf, false, indent + 2);
                        }

                        EmitRdfArrayTag(node, false, indent + 1);
                    }
                    else if (!hasRdfResourceQual)
                    {
                        // This is a "normal" struct, use the rdf:parseType="Resource" form.
                        if (!node.HasChildren)
                        {
                            // Change serialization to canonical format with inner rdf:Description-tag
                            // if option is set
                            if (useCanonicalRdf)
                            {
                                Write(">");
                                WriteNewline();
                                WriteIndent(indent + 1);
                                Write(RdfEmptyStruct);
                            }
                            else
                            {
                                Write(" rdf:parseType=\"Resource\"/>");
                                emitEndTag = false;
                            }

                            WriteNewline();
                        }
                        else
                        {
                            // Change serialization to canonical format with inner rdf:Description-tag
                            // if option is set
                            if (useCanonicalRdf)
                            {
                                Write(">");
                                WriteNewline();
                                indent++;
                                WriteIndent(indent);
                                Write(RdfStructStart);
                                Write(">");
                            }
                            else
                            {
                                Write(" rdf:parseType=\"Resource\">");
                            }

                            WriteNewline();
                            for (var it = node.IterateChildren(); it.HasNext();)
                            {
                                var child = (XmpNode)it.Next();
                                SerializeCanonicalRdfProperty(child, useCanonicalRdf, false, indent + 1);
                            }

                            if (useCanonicalRdf)
                            {
                                WriteIndent(indent);
                                Write(RdfStructEnd);
                                WriteNewline();
                                indent--;
                            }
                        }
                    }
                    else
                    {
                        // This is a struct with an rdf:resource attribute, use the
                        // "empty property element" form.
                        for (var it1 = node.IterateChildren(); it1.HasNext();)
                        {
                            var child = (XmpNode)it1.Next();
                            if (!CanBeRdfAttrProp(child))
                            {
                                throw new XmpException("Can't mix rdf:resource and complex fields", XmpErrorCode.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();
            }
        }
Exemple #9
0
        /// <summary>Follow an expanded path expression to find or create a node.</summary>
        /// <param name="xmpTree">the node to begin the search.</param>
        /// <param name="xpath">the complete xpath</param>
        /// <param name="createNodes">
        /// flag if nodes shall be created
        /// (when called by <c>setProperty()</c>)
        /// </param>
        /// <param name="leafOptions">
        /// the options for the created leaf nodes (only when
        /// <c>createNodes == true</c>).
        /// </param>
        /// <returns>Returns the node if found or created or <c>null</c>.</returns>
        /// <exception cref="XmpException">
        /// An exception is only thrown if an error occurred,
        /// not if a node was not found.
        /// </exception>
        internal static XmpNode FindNode(XmpNode xmpTree, XmpPath xpath, bool createNodes, PropertyOptions leafOptions)
        {
            // check if xpath is set.
            if (xpath == null || xpath.Size() == 0)
            {
                throw new XmpException("Empty XmpPath", XmpErrorCode.BadXPath);
            }

            // Root of implicitly created subtree to possible delete it later.
            // Valid only if leaf is new.
            XmpNode rootImplicitNode = null;

            // resolve schema step
            var currNode = FindSchemaNode(xmpTree, xpath.GetSegment(XmpPath.StepSchema).Name, createNodes);

            if (currNode == null)
            {
                return(null);
            }

            if (currNode.IsImplicit)
            {
                currNode.IsImplicit = false;
                // Clear the implicit node bit.
                rootImplicitNode = currNode;
            }

            // Save the top most implicit node.
            // Now follow the remaining steps of the original XmpPath.
            try
            {
                for (var i = 1; i < xpath.Size(); i++)
                {
                    currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes);
                    if (currNode == null)
                    {
                        if (createNodes)
                        {
                            // delete implicitly created nodes
                            DeleteNode(rootImplicitNode);
                        }
                        return(null);
                    }

                    if (currNode.IsImplicit)
                    {
                        // clear the implicit node flag
                        currNode.IsImplicit = false;
                        // if node is an ALIAS (can be only in root step, auto-create array
                        // when the path has been resolved from a not simple alias type
                        if (i == 1 && xpath.GetSegment(i).IsAlias&& xpath.GetSegment(i).AliasForm != 0)
                        {
                            currNode.Options.SetOption(xpath.GetSegment(i).AliasForm, true);
                        }
                        else
                        {
                            // "CheckImplicitStruct" in C++
                            if (i < xpath.Size() - 1 && xpath.GetSegment(i).Kind == XmpPathStepType.StructFieldStep && !currNode.Options.IsCompositeProperty)
                            {
                                currNode.Options.IsStruct = true;
                            }
                        }

                        if (rootImplicitNode == null)
                        {
                            rootImplicitNode = currNode;
                        }
                    }
                }
            }
            catch (XmpException)
            {
                // Save the top most implicit node.
                // if new notes have been created prior to the error, delete them
                if (rootImplicitNode != null)
                {
                    DeleteNode(rootImplicitNode);
                }

                throw;
            }

            if (rootImplicitNode != null)
            {
                // set options only if a node has been successful created
                currNode.Options.MergeWith(leafOptions);
                currNode.Options = currNode.Options;
            }

            return(currNode);
        }
Exemple #10
0
 /// <summary>Find or create a schema node if <c>createNodes</c> is false and</summary>
 /// <param name="tree">the root of the xmp tree.</param>
 /// <param name="namespaceUri">a namespace</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, bool createNodes)
 {
     return(FindSchemaNode(tree, namespaceUri, null, createNodes));
 }
Exemple #11
0
        /// <summary>
        /// After processing by ExpandXPath, a step can be of these forms:
        /// </summary>
        /// <remarks>
        /// After processing by ExpandXPath, a step can be of these forms:
        /// <list type="bullet">
        /// <item>qualName - A top level property or struct field.</item>
        /// <item>[index] - An element of an array.</item>
        /// <item>[last()] - The last element of an array.</item>
        /// <item>[qualName="value"] - An element in an array of structs, chosen by a field value.</item>
        /// <item>[?qualName="value"] - An element in an array, chosen by a qualifier value.</item>
        /// <item>?qualName - A general qualifier.</item>
        /// </list>
        /// Find the appropriate child node, resolving aliases, and optionally creating nodes.
        /// </remarks>
        /// <param name="parentNode">the node to start to start from</param>
        /// <param name="nextStep">the xpath segment</param>
        /// <param name="createNodes"></param>
        /// <returns>returns the found or created XMPPath node</returns>
        /// <exception cref="XmpException"></exception>
        private static XmpNode FollowXPathStep(XmpNode parentNode, XmpPathSegment nextStep, bool createNodes)
        {
            XmpNode nextNode = null;
            var     stepKind = nextStep.Kind;

            if (stepKind == XmpPath.StructFieldStep)
            {
                nextNode = FindChildNode(parentNode, nextStep.Name, createNodes);
            }
            else
            {
                if (stepKind == XmpPath.QualifierStep)
                {
                    nextNode = FindQualifierNode(parentNode, nextStep.Name.Substring(1), createNodes);
                }
                else
                {
                    // This is an array indexing step. First get the index, then get the node.
                    if (!parentNode.Options.IsArray)
                    {
                        throw new XmpException("Indexing applied to non-array", XmpErrorCode.BadXPath);
                    }
                    var index = 0;
                    if (stepKind == XmpPath.ArrayIndexStep)
                    {
                        index = FindIndexedItem(parentNode, nextStep.Name, createNodes);
                    }
                    else
                    {
                        if (stepKind == XmpPath.ArrayLastStep)
                        {
                            index = parentNode.GetChildrenLength();
                        }
                        else
                        {
                            if (stepKind == XmpPath.FieldSelectorStep)
                            {
                                var result     = Utils.SplitNameAndValue(nextStep.Name);
                                var fieldName  = result[0];
                                var fieldValue = result[1];
                                index = LookupFieldSelector(parentNode, fieldName, fieldValue);
                            }
                            else
                            {
                                if (stepKind == XmpPath.QualSelectorStep)
                                {
                                    var result    = Utils.SplitNameAndValue(nextStep.Name);
                                    var qualName  = result[0];
                                    var qualValue = result[1];
                                    index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.AliasForm);
                                }
                                else
                                {
                                    throw new XmpException("Unknown array indexing step in FollowXPathStep", XmpErrorCode.InternalFailure);
                                }
                            }
                        }
                    }
                    if (1 <= index && index <= parentNode.GetChildrenLength())
                    {
                        nextNode = parentNode.GetChild(index);
                    }
                }
            }
            return(nextNode);
        }
        /// <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 != rightNode.Value)
                {
                    return(false);
                }
                if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage)
                {
                    return(false);
                }
                if (leftNode.Options.HasLanguage && leftNode.GetQualifier(1).Value != 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);
        }
        /// <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.
                        XmpMeta.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 (!Equals(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 || sourceItem.GetQualifier(1).Name != XmpConstants.XmlLang)
                                {
                                    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 (sourceItem.GetQualifier(1).Value != XmpConstants.XDefault || !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);
                                }
                            }
                        }
                    }
                }
            }
        }
Exemple #14
0
 /// <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;
 }
Exemple #15
0
 /// <summary>Adds a node as child to this node.</summary>
 /// <param name="index">
 /// the index of the node <em>before</em> which the new one is inserted.
 /// <em>Note:</em> The node children are indexed from [1..size]!
 /// An index of size + 1 appends a node.
 /// </param>
 /// <param name="node">an XMPNode</param>
 /// <exception cref="XmpException"></exception>
 public void AddChild(int index, XmpNode node)
 {
     AssertChildNotExisting(node.Name);
     node.Parent = this;
     GetChildren().Insert(index - 1, node);
 }
        /// <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="System.IO.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.
            var hasAttrFields = false;
            var hasElemFields = false;
            var emitEndTag    = true;

            for (var ic = node.IterateChildren(); ic.HasNext();)
            {
                var field = (XmpNode)ic.Next();

                if (CanBeRdfAttrProp(field))
                {
                    hasAttrFields = true;
                }
                else
                {
                    hasElemFields = true;
                }

                if (hasAttrFields && hasElemFields)
                {
                    break;
                }
            }

            // No sense looking further.
            if (hasRdfResourceQual && hasElemFields)
            {
                throw new XmpException("Can't mix rdf:resource qualifier and element fields", XmpErrorCode.BadRdf);
            }

            if (!node.HasChildren)
            {
                // Catch an empty struct as a special case. The case
                // below would emit an empty
                // XML element, which gets reparsed as a simple property
                // with an empty value.
                Write(" rdf:parseType=\"Resource\"/>");
                WriteNewline();
                emitEndTag = false;
            }
            else if (!hasElemFields)
            {
                // All fields can be attributes, use the
                // emptyPropertyElt form.
                SerializeCompactRdfAttrProps(node, indent + 1);
                Write("/>");
                WriteNewline();
                emitEndTag = false;
            }
            else if (!hasAttrFields)
            {
                // All fields must be elements, use the
                // parseTypeResourcePropertyElt form.
                Write(" rdf:parseType=\"Resource\">");
                WriteNewline();
                SerializeCompactRdfElementProps(node, indent + 1);
            }
            else
            {
                // Have a mix of attributes and elements, use an inner rdf:Description.
                Write('>');
                WriteNewline();
                WriteIndent(indent + 1);
                Write(RdfStructStart);
                SerializeCompactRdfAttrProps(node, indent + 2);
                Write(">");
                WriteNewline();
                SerializeCompactRdfElementProps(node, indent + 1);
                WriteIndent(indent + 1);
                Write(RdfStructEnd);
                WriteNewline();
            }

            return(emitEndTag);
        }
Exemple #17
0
 /// <summary>Replaces a node with another one.</summary>
 /// <param name="index">
 /// the index of the node that will be replaced.
 /// <em>Note:</em> The node children are indexed from [1..size]!
 /// </param>
 /// <param name="node">the replacement XMPNode</param>
 public void ReplaceChild(int index, XmpNode node)
 {
     node.Parent = this;
     GetChildren()[index - 1] = node;
 }
            /// <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 XmpPropertyInfo(node, baseNs, path, value));
            }
Exemple #19
0
 /// <summary>Removes a child node.</summary>
 /// <remarks>
 /// Removes a child node.
 /// If its a schema node and doesn't have any children anymore, its deleted.
 /// </remarks>
 /// <param name="node">the child node to delete.</param>
 public void RemoveChild(XmpNode node)
 {
     GetChildren().Remove(node);
     CleanupChildren();
 }
Exemple #20
0
        /// <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 below ignore attribute qualifiers such as
        /// xml:lang, they don't affect the output form.
        /// <code>
        /// &lt;ns:UnqualifiedStructProperty-1
        /// ... The fields as attributes, if all are simple and unqualified
        /// /&gt;
        /// &lt;ns:UnqualifiedStructProperty-2 rdf:parseType=&quot;Resource&quot;&gt;
        /// ... The fields as elements, if none are simple and unqualified
        /// &lt;/ns:UnqualifiedStructProperty-2&gt;
        /// &lt;ns:UnqualifiedStructProperty-3&gt;
        /// &lt;rdf:Description
        /// ... The simple and unqualified fields as attributes
        /// &gt;
        /// ... The compound or qualified fields as elements
        /// &lt;/rdf:Description&gt;
        /// &lt;/ns:UnqualifiedStructProperty-3&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 rdf:parseType=&quot;Resource&quot;&gt;
        /// &lt;rdf:value&gt; ... Property &quot;value&quot;
        /// following the unqualified forms ... &lt;/rdf:value&gt;
        /// ... Qualifiers looking like named struct fields
        /// &lt;/ns:QualifiedProperty&gt;
        /// </code>
        /// *** 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 (elemName == XmpConstants.ArrayItemName)
                {
                    elemName = XmpConstants.RdfLi;
                }

                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 = qualifier.Name == "rdf:resource";
                        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>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;
            }
        }