/// <summary> /// 7.2.18 parseTypeResourcePropertyElt /// start-element ( URI == propertyElementURIs, /// attributes == set ( idAttr?, parseResource ) ) /// propertyEltList /// end-element() /// /// Add a new struct node with a qualifier for the possible rdf:ID attribute. /// Then process the XML child nodes to get the struct fields. /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfParseTypeResourcePropertyElement(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, bool isTopLevel) { XmpNode newStruct = AddChildNode(xmp, xmpParent, xmlNode, "", isTopLevel); newStruct.Options.Struct = true; if (xmlNode.Attributes != null) { for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes[i]; if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } string attrLocal = attribute.LocalName; string attrNs = attribute.NamespaceURI; if (XML_LANG.Equals(attribute.Name)) { AddQualifierNode(newStruct, XML_LANG, attribute.Value); } else if (NS_RDF.Equals(attrNs) && ("ID".Equals(attrLocal) || "parseType".Equals(attrLocal))) { continue; // The caller ensured the value is "Resource". // Ignore all rdf:ID attributes. } throw new XmpException("Invalid attribute for ParseTypeResource property element", XmpError.BADRDF); } } RdfPropertyElementList(xmp, newStruct, xmlNode, false); if (newStruct.HasValueChild) { FixupQualifiedNode(newStruct); } }
/// <summary> /// 7.2.13 propertyEltList /// ws* ( propertyElt ws* )* /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlParent"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfPropertyElementList(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlParent, bool isTopLevel) { for (int i = 0; i < xmlParent.ChildNodes.Count; i++) { XmlNode currChild = xmlParent.ChildNodes[i]; if (IsWhitespaceNode(currChild)) { continue; } if (currChild.NodeType != XmlNodeType.Element) { throw new XmpException("Expected property element node not found", XmpError.BADRDF); } RdfPropertyElement(xmp, xmpParent, currChild, isTopLevel); } }
/// <summary> /// 7.2.15 resourcePropertyElt /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) /// ws* nodeElement ws* /// end-element() /// /// This handles structs using an rdf:Description node, /// arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified /// properties written with rdf:Description and rdf:value. /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfResourcePropertyElement(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, bool isTopLevel) { if (isTopLevel && "iX:changes".Equals(xmlNode.Name)) { // Strip old "punchcard" chaff which has on the prefix "iX:". return; } XmpNode newCompound = AddChildNode(xmp, xmpParent, xmlNode, "", isTopLevel); // walk through the attributes if (xmlNode.Attributes != null) { for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes[i]; if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } string attrLocal = attribute.LocalName; string attrNs = attribute.NamespaceURI; if (XML_LANG.Equals(attribute.Name)) { AddQualifierNode(newCompound, XML_LANG, attribute.Value); } else if ("ID".Equals(attrLocal) && NS_RDF.Equals(attrNs)) { continue; // Ignore all rdf:ID attributes. } throw new XmpException("Invalid attribute for resource property element", XmpError.BADRDF); } } // walk through the children bool found = false; for (int i = 0; i < xmlNode.ChildNodes.Count; i++) { XmlNode currChild = xmlNode.ChildNodes[i]; if (!IsWhitespaceNode(currChild)) { if (currChild.NodeType == XmlNodeType.Element && !found) { bool isRdf = NS_RDF.Equals(currChild.NamespaceURI); string childLocal = currChild.LocalName; if (isRdf && "Bag".Equals(childLocal)) { newCompound.Options.Array = true; } else if (isRdf && "Seq".Equals(childLocal)) { newCompound.Options.Array = true; newCompound.Options.ArrayOrdered = true; } else if (isRdf && "Alt".Equals(childLocal)) { newCompound.Options.Array = true; newCompound.Options.ArrayOrdered = true; newCompound.Options.ArrayAlternate = true; } else { newCompound.Options.Struct = true; if (!isRdf && !"Description".Equals(childLocal)) { string typeName = currChild.NamespaceURI; if (typeName == null) { throw new XmpException("All XML elements must be in a namespace", XmpError.BADXMP); } typeName += ':' + childLocal; AddQualifierNode(newCompound, "rdf:type", typeName); } } RdfNodeElement(xmp, newCompound, currChild, false); if (newCompound.HasValueChild) { FixupQualifiedNode(newCompound); } else if (newCompound.Options.ArrayAlternate) { XmpNodeUtils.DetectAltText(newCompound); } found = true; } else if (found) { // found second child element throw new XmpException("Invalid child of resource property element", XmpError.BADRDF); } else { throw new XmpException("Children of resource property element must be XML elements", XmpError.BADRDF); } } } if (!found) { // didn't found any child elements throw new XmpException("Missing child of resource property element", XmpError.BADRDF); } }
/// <summary> /// Constructor </summary> /// <param name="parentNode"> the node which children shall be iterated. </param> /// <param name="parentPath"> the full path of the former node without the leaf node. </param> public NodeIteratorChildren(XmpIteratorImpl outerInstance, XmpNode parentNode, string parentPath) : base(outerInstance, parentNode, parentPath, 0) { _outerInstance = outerInstance; if (parentNode.Options.SchemaNode) { outerInstance.BaseNs = parentNode.Name; } _parentPath = AccumulatePath(parentNode, parentPath, 1); _childrenIterator = parentNode.IterateChildren(); }
/// <summary> /// 7.2.5 nodeElementURIs /// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) /// /// 7.2.11 nodeElement /// start-element ( URI == nodeElementURIs, /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) /// propertyEltList /// end-element() /// /// A node element URI is rdf:Description or anything else that is not an RDF /// term. /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfNodeElement(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, bool isTopLevel) { int nodeTerm = GetRdfTermKind(xmlNode); if (nodeTerm != RDFTERM_DESCRIPTION && nodeTerm != RDFTERM_OTHER) { throw new XmpException("Node element must be rdf:Description or typed node", XmpError.BADRDF); } if (isTopLevel && nodeTerm == RDFTERM_OTHER) { throw new XmpException("Top level typed node not allowed", XmpError.BADXMP); } RdfNodeElementAttrs(xmp, xmpParent, xmlNode, isTopLevel); RdfPropertyElementList(xmp, xmpParent, xmlNode, isTopLevel); }
public XmpPropertyImpl1(XmpNode itemNode) { _itemNode = itemNode; }
// ------------------------------------------------------------------------------------- // private /// <summary> /// Locate or create the item node and set the value. Note the index /// parameter is one-based! The index can be in the range [1..size + 1] or /// "last()", normalize it and check the insert flags. The order of the /// normalization checks is important. If the array is empty we end up with /// an index and location to set item size + 1. /// </summary> /// <param name="arrayNode"> an array node </param> /// <param name="itemIndex"> the index where to insert the item </param> /// <param name="itemValue"> the item value </param> /// <param name="itemOptions"> the options for the new item </param> /// <param name="insert"> insert oder overwrite at index position? </param> /// <exception cref="XmpException"> </exception> private void DoSetArrayItem(XmpNode arrayNode, int itemIndex, string itemValue, PropertyOptions itemOptions, bool insert) { XmpNode itemNode = new XmpNode(ARRAY_ITEM_NAME, null); itemOptions = XmpNodeUtils.VerifySetOptions(itemOptions, itemValue); // in insert mode the index after the last is allowed, // even ARRAY_LAST_ITEM points to the index *after* the last. int maxIndex = insert ? arrayNode.ChildrenLength + 1 : arrayNode.ChildrenLength; if (itemIndex == ARRAY_LAST_ITEM) { itemIndex = maxIndex; } if (1 <= itemIndex && itemIndex <= maxIndex) { if (!insert) { arrayNode.RemoveChild(itemIndex); } arrayNode.AddChild(itemIndex, itemNode); SetNode(itemNode, itemValue, itemOptions, false); } else { throw new XmpException("Array index out of bounds", XmpError.BADINDEX); } }
/// <summary> /// The initial support for WAV files mapped a legacy ID3 audio copyright /// into a new xmpDM:copyright property. This is special case code to migrate /// that into dc:rights['x-default']. The rules: /// /// <pre> /// 1. If there is no dc:rights array, or an empty array - /// Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright. /// /// 2. If there is a dc:rights array but it has no x-default item - /// Create an x-default item as a copy of the first item then apply rule #3. /// /// 3. If there is a dc:rights array with an x-default item, /// Look for a double linefeed in the value. /// A. If no double linefeed, compare the x-default value to the xmpDM:copyright value. /// A1. If they match then leave the x-default value alone. /// A2. Otherwise, append a double linefeed and /// the xmpDM:copyright value to the x-default value. /// B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value. /// B1. If they match then leave the x-default value alone. /// B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value. /// /// 4. In all cases, delete the xmpDM:copyright property. /// </pre> /// </summary> /// <param name="xmp"> the metadata object </param> /// <param name="dmCopyright"> the "dm:copyright"-property </param> private static void MigrateAudioCopyright(XMPMeta xmp, XmpNode dmCopyright) { try { XmpNode dcSchema = XmpNodeUtils.FindSchemaNode(((XmpMetaImpl)xmp).Root, XmpConst.NS_DC, true); string dmValue = dmCopyright.Value; const string doubleLf = "\n\n"; XmpNode dcRightsArray = XmpNodeUtils.FindChildNode(dcSchema, "dc:rights", false); if (dcRightsArray == null || !dcRightsArray.HasChildren()) { // 1. No dc:rights array, create from double linefeed and xmpDM:copyright. dmValue = doubleLf + dmValue; xmp.SetLocalizedText(XmpConst.NS_DC, "rights", "", XmpConst.X_DEFAULT, dmValue, null); } else { int xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConst.X_DEFAULT); if (xdIndex < 0) { // 2. No x-default item, create from the first item. string firstValue = dcRightsArray.GetChild(1).Value; xmp.SetLocalizedText(XmpConst.NS_DC, "rights", "", XmpConst.X_DEFAULT, firstValue, null); xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConst.X_DEFAULT); } // 3. Look for a double linefeed in the x-default value. XmpNode defaultNode = dcRightsArray.GetChild(xdIndex); string defaultValue = defaultNode.Value; int lfPos = defaultValue.IndexOf(doubleLf); if (lfPos < 0) { // 3A. No double LF, compare whole values. if (!dmValue.Equals(defaultValue)) { // 3A2. Append the xmpDM:copyright to the x-default // item. defaultNode.Value = defaultValue + doubleLf + dmValue; } } else { // 3B. Has double LF, compare the tail. if (!defaultValue.Substring(lfPos + 2).Equals(dmValue)) { // 3B2. Replace the x-default tail. defaultNode.Value = defaultValue.Substring(0, lfPos + 2) + dmValue; } } } // 4. Get rid of the xmpDM:copyright. dmCopyright.Parent.RemoveChild(dmCopyright); } catch (XmpException) { // Don't let failures (like a bad dc:rights form) stop other // cleanup. } }
/// <summary> /// 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 virtual void AddChild(int index, XmpNode node) { AssertChildNotExisting(node.Name); node.Parent = this; Children.Insert(index - 1, node); }
/// <summary> /// Compares two nodes including its children and qualifier. </summary> /// <param name="leftNode"> an <code>XMPNode</code> </param> /// <param name="rightNode"> an <code>XMPNode</code> </param> /// <returns> Returns true if the nodes are equal, false otherwise. </returns> /// <exception cref="XmpException"> Forwards exceptions to the calling method. </exception> private static bool ItemValuesMatch(XmpNode leftNode, XmpNode rightNode) { PropertyOptions leftForm = leftNode.Options; PropertyOptions rightForm = rightNode.Options; if (leftForm.Equals(rightForm)) { return(false); } if (leftForm.Options == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.Value.Equals(rightNode.Value)) { return(false); } if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage) { return(false); } if (leftNode.Options.HasLanguage && !leftNode.GetQualifier(1).Value.Equals(rightNode.GetQualifier(1).Value)) { return(false); } } else if (leftForm.Struct) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.ChildrenLength != rightNode.ChildrenLength) { return(false); } for (IEnumerator it = leftNode.IterateChildren(); it.MoveNext();) { XmpNode leftField = (XmpNode)it.Current; if (leftField == null) { continue; } XmpNode rightField = XmpNodeUtils.FindChildNode(rightNode, leftField.Name, false); if (rightField == null || !ItemValuesMatch(leftField, rightField)) { return(false); } } } else { // Array nodes, see if the "leftNode" values are present in the // "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the // destination for AppendProperties. Debug.Assert(leftForm.Array); for (IEnumerator il = leftNode.IterateChildren(); il.MoveNext();) { XmpNode leftItem = (XmpNode)il.Current; if (leftItem == null) { continue; } bool match = false; for (IEnumerator ir = rightNode.IterateChildren(); ir.MoveNext();) { XmpNode rightItem = (XmpNode)ir.Current; if (rightItem == null) { continue; } if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return(false); } } } return(true); // All of the checks passed. }
/// <summary> /// Visit all of the top level nodes looking for aliases. If there is /// no base, transplant the alias subtree. If there is a base and strict /// aliasing is on, make sure the alias and base subtrees match. /// </summary> /// <param name="tree"> the root of the metadata tree </param> /// <param name="options"> th parsing options </param> /// <exception cref="XmpException"> Forwards XMP errors </exception> private static void MoveExplicitAliases(XmpNode tree, ParseOptions options) { if (!tree.HasAliases) { return; } tree.HasAliases = false; bool strictAliasing = options.StrictAliasing; IEnumerator schemaIt = tree.UnmodifiableChildren.GetEnumerator(); while (schemaIt.MoveNext()) { XmpNode currSchema = (XmpNode)schemaIt.Current; if (currSchema == null) { continue; } if (!currSchema.HasAliases) { continue; } ArrayList currPropsToRemove = new ArrayList(); IEnumerator propertyIt = currSchema.IterateChildren(); while (propertyIt.MoveNext()) { XmpNode currProp = (XmpNode)propertyIt.Current; if (currProp == null) { continue; } if (!currProp.Alias) { continue; } currProp.Alias = false; // Find the base path, look for the base schema and root node. XMPAliasInfo info = XMPMetaFactory.SchemaRegistry.FindAlias(currProp.Name); if (info != null) { // find or create schema XmpNode baseSchema = XmpNodeUtils.FindSchemaNode(tree, info.Namespace, null, true); baseSchema.Implicit = false; XmpNode baseNode = XmpNodeUtils.FindChildNode(baseSchema, info.Prefix + info.PropName, false); if (baseNode == null) { if (info.AliasForm.Simple) { // A top-to-top alias, transplant the property. // change the alias property name to the base name string qname = info.Prefix + info.PropName; currProp.Name = qname; baseSchema.AddChild(currProp); } else { // An alias to an array item, // create the array and transplant the property. baseNode = new XmpNode(info.Prefix + info.PropName, info.AliasForm.ToPropertyOptions()); baseSchema.AddChild(baseNode); TransplantArrayItemAlias(currProp, baseNode); } currPropsToRemove.Add(currProp); } else if (info.AliasForm.Simple) { // The base node does exist and this is a top-to-top alias. // Check for conflicts if strict aliasing is on. // Remove and delete the alias subtree. if (strictAliasing) { CompareAliasedSubtrees(currProp, baseNode, true); } currPropsToRemove.Add(currProp); } else { // This is an alias to an array item and the array exists. // Look for the aliased item. // Then transplant or check & delete as appropriate. XmpNode itemNode = null; if (info.AliasForm.ArrayAltText) { int xdIndex = XmpNodeUtils.LookupLanguageItem(baseNode, XmpConst.X_DEFAULT); if (xdIndex != -1) { itemNode = baseNode.GetChild(xdIndex); } } else if (baseNode.HasChildren()) { itemNode = baseNode.GetChild(1); } if (itemNode == null) { TransplantArrayItemAlias(currProp, baseNode); } else { if (strictAliasing) { CompareAliasedSubtrees(currProp, itemNode, true); } } currPropsToRemove.Add(currProp); } } } foreach (object o in currPropsToRemove) { currSchema.Children.Remove(o); } currPropsToRemove.Clear(); currSchema.HasAliases = false; } }
/// <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); } } } } }
/// <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(); } }
/// <summary> /// see {@link XMPUtils#separateArrayItems(XMPMeta, String, String, String, /// PropertyOptions, boolean)} /// </summary> /// <param name="xmp"> /// The XMP object containing the array to be updated. </param> /// <param name="schemaNs"> /// The schema namespace URI for the array. Must not be null or /// the empty string. </param> /// <param name="arrayName"> /// The name of the array. May be a general path expression, must /// not be null or the empty string. Each item in the array must /// be a simple string value. </param> /// <param name="catedStr"> /// The string to be separated into the array items. </param> /// <param name="arrayOptions"> /// Option flags to control the separation. </param> /// <param name="preserveCommas"> /// Flag if commas shall be preserved /// </param> /// <exception cref="XmpException"> /// Forwards the Exceptions from the metadata processing </exception> public static void SeparateArrayItems(IXmpMeta xmp, string schemaNs, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); if (catedStr == null) { throw new XmpException("Parameter must not be null", XmpError.BADPARAM); } ParameterAsserts.AssertImplementation(xmp); XmpMetaImpl xmpImpl = (XmpMetaImpl)xmp; // Keep a zero value, has special meaning below. XmpNode arrayNode = SeparateFindCreateArray(schemaNs, arrayName, arrayOptions, xmpImpl); // Extract the item values one at a time, until the whole input string is done. int charKind = UCK_NORMAL; char ch = (char)0; int itemEnd = 0; int endPos = catedStr.Length; while (itemEnd < endPos) { string itemValue; int itemStart; // Skip any leading spaces and separation characters. Always skip commas here. // They can be kept when within a value, but not when alone between values. for (itemStart = itemEnd; itemStart < endPos; itemStart++) { ch = catedStr[itemStart]; charKind = ClassifyCharacter(ch); if (charKind == UCK_NORMAL || charKind == UCK_QUOTE) { break; } } if (itemStart >= endPos) { break; } int nextKind; if (charKind != UCK_QUOTE) { // This is not a quoted value. Scan for the end, create an array // item from the substring. for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind == UCK_NORMAL || charKind == UCK_QUOTE || (charKind == UCK_COMMA && preserveCommas)) { continue; } if (charKind != UCK_SPACE) { break; } if ((itemEnd + 1) < endPos) { ch = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(ch); if (nextKind == UCK_NORMAL || nextKind == UCK_QUOTE || (nextKind == UCK_COMMA && preserveCommas)) { continue; } } // Anything left? break; // Have multiple spaces, or a space followed by a // separator. } itemValue = catedStr.Substring(itemStart, itemEnd - itemStart); } else { // Accumulate quoted values into a local string, undoubling // internal quotes that // match the surrounding quotes. Do not undouble "unmatching" // quotes. char openQuote = ch; char closeQuote = GetClosingQuote(openQuote); itemStart++; // Skip the opening quote; itemValue = ""; for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind != UCK_QUOTE || !IsSurroundingQuote(ch, openQuote, closeQuote)) { // This is not a matching quote, just append it to the // item value. itemValue += ch; } else { // This is a "matching" quote. Is it doubled, or the // final closing quote? // Tolerate various edge cases like undoubled opening // (non-closing) quotes, // or end of input. char nextChar; if ((itemEnd + 1) < endPos) { nextChar = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(nextChar); } else { nextKind = UCK_SEMICOLON; nextChar = (char)0x3B; } if (ch == nextChar) { // This is doubled, copy it and skip the double. itemValue += ch; // Loop will add in charSize. itemEnd++; } else if (!IsClosingingQuote(ch, openQuote, closeQuote)) { // This is an undoubled, non-closing quote, copy it. itemValue += ch; } else { // This is an undoubled closing quote, skip it and // exit the loop. itemEnd++; break; } } } } // Add the separated item to the array. // Keep a matching old value in case it had separators. int foundIndex = -1; for (int oldChild = 1; oldChild <= arrayNode.ChildrenLength; oldChild++) { if (itemValue.Equals(arrayNode.GetChild(oldChild).Value)) { foundIndex = oldChild; break; } } if (foundIndex < 0) { XmpNode newItem = new XmpNode(ARRAY_ITEM_NAME, itemValue, null); arrayNode.AddChild(newItem); } } }
/// <summary> /// Adds a child node. /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="value"> Node value </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <returns> Returns the newly created child node. </returns> /// <exception cref="XmpException"> thown on parsing errors </exception> private static XmpNode AddChildNode(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, string value, bool isTopLevel) { IXmpSchemaRegistry registry = XmpMetaFactory.SchemaRegistry; string @namespace = xmlNode.NamespaceURI; string childName; if (@namespace != null) { if (NS_DC_DEPRECATED.Equals(@namespace)) { // Fix a legacy DC namespace @namespace = NS_DC; } string prefix = registry.GetNamespacePrefix(@namespace); if (prefix == null) { prefix = xmlNode.Prefix ?? DEFAULT_PREFIX; prefix = registry.RegisterNamespace(@namespace, prefix); } childName = prefix + xmlNode.LocalName; } else { throw new XmpException("XML namespace required for all elements and attributes", XmpError.BADRDF); } // create schema node if not already there PropertyOptions childOptions = new PropertyOptions(); bool isAlias = false; if (isTopLevel) { // Lookup the schema node, adjust the XMP parent pointer. // Incoming parent must be the tree root. XmpNode schemaNode = XmpNodeUtils.FindSchemaNode(xmp.Root, @namespace, DEFAULT_PREFIX, true); schemaNode.Implicit = false; // Clear the implicit node bit. // need runtime check for proper 32 bit code. xmpParent = schemaNode; // If this is an alias set the alias flag in the node // and the hasAliases flag in the tree. if (registry.FindAlias(childName) != null) { isAlias = true; xmp.Root.HasAliases = true; schemaNode.HasAliases = true; } } // Make sure that this is not a duplicate of a named node. bool isArrayItem = "rdf:li".Equals(childName); bool isValueNode = "rdf:value".Equals(childName); // Create XMP node and so some checks XmpNode newChild = new XmpNode(childName, value, childOptions); newChild.Alias = isAlias; // Add the new child to the XMP parent node, a value node first. if (!isValueNode) { xmpParent.AddChild(newChild); } else { xmpParent.AddChild(1, newChild); } if (isValueNode) { if (isTopLevel || !xmpParent.Options.Struct) { throw new XmpException("Misplaced rdf:value element", XmpError.BADRDF); } xmpParent.HasValueChild = true; } if (isArrayItem) { if (!xmpParent.Options.Array) { throw new XmpException("Misplaced rdf:li element", XmpError.BADRDF); } newChild.Name = ARRAY_ITEM_NAME; } return newChild; }
/// <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 virtual void ReplaceChild(int index, XmpNode node) { node.Parent = this; Children[index - 1] = node; }
/// <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); } }
/// <summary> /// Removes a child node. /// If its a schema node and doesn't have any children anymore, its deleted. /// </summary> /// <param name="node"> the child node to delete. </param> public virtual void RemoveChild(XmpNode node) { Children.Remove(node); CleanupChildren(); }
/// <summary> /// Constructor for an empty metadata object. /// </summary> public XmpMetaImpl() { // create root node _tree = new XmpNode(null, null, null); }
//------------------------------------------------------------------------------ private methods /// <summary> /// Dumps this node and its qualifier and children recursively. /// <em>Note:</em> It creats empty options on every node. /// </summary> /// <param name="result"> the buffer to append the dump. </param> /// <param name="recursive"> Flag is qualifier and child nodes shall be rendered too </param> /// <param name="indent"> the current indent level. </param> /// <param name="index"> the index within the parent node (important for arrays) </param> private void DumpNode(StringBuilder result, bool recursive, int indent, int index) { // write indent for (int i = 0; i < indent; i++) { result.Append('\t'); } // render Node if (_parent != null) { if (Options.Qualifier) { result.Append('?'); result.Append(_name); } else if (Parent.Options.Array) { result.Append('['); result.Append(index); result.Append(']'); } else { result.Append(_name); } } else { // applies only to the root node result.Append("ROOT NODE"); if (!String.IsNullOrEmpty(_name)) { // the "about" attribute result.Append(" ("); result.Append(_name); result.Append(')'); } } if (!String.IsNullOrEmpty(_value)) { result.Append(" = \""); result.Append(_value); result.Append('"'); } // render options if at least one is set if (Options.ContainsOneOf(0xffffffff)) { result.Append("\t("); result.Append(Options.ToString()); result.Append(" : "); result.Append(Options.OptionsString); result.Append(')'); } result.Append('\n'); // render qualifier if (recursive && HasQualifier()) { XmpNode[] quals = new XmpNode[Qualifier.Count]; Qualifier.CopyTo(quals, 0); int i = 0; while (quals.Length > i && (XmpConst.XML_LANG.Equals(quals[i].Name) || "rdf:type".Equals(quals[i].Name))) { i++; } Array.Sort(quals, i, quals.Length - i); for (i = 0; i < quals.Length; i++) { XmpNode qualifier = quals[i]; qualifier.DumpNode(result, recursive, indent + 2, i + 1); } } // render children if (recursive && HasChildren()) { XmpNode[] children = new XmpNode[Children.Count]; Children.CopyTo(children, 0); if (!Options.Array) { Array.Sort(children); } for (int i = 0; i < children.Length; i++) { XmpNode child = children[i]; child.DumpNode(result, recursive, indent + 1, i + 1); } } }
/// <seealso cref= XMPUtils#catenateArrayItems(XMPMeta, String, String, String, String, /// boolean) /// </seealso> /// <param name="xmp"> /// The XMP object containing the array to be catenated. </param> /// <param name="schemaNs"> /// The schema namespace URI for the array. Must not be null or /// the empty string. </param> /// <param name="arrayName"> /// The name of the array. May be a general path expression, must /// not be null or the empty string. Each item in the array must /// be a simple string value. </param> /// <param name="separator"> /// The string to be used to separate the items in the catenated /// string. Defaults to "; ", ASCII semicolon and space /// (U+003B, U+0020). </param> /// <param name="quotes"> /// The characters to be used as quotes around array items that /// contain a separator. Defaults to '"' </param> /// <param name="allowCommas"> /// Option flag to control the catenation. </param> /// <returns> Returns the string containing the catenated array items. </returns> /// <exception cref="XmpException"> /// Forwards the Exceptions from the metadata processing </exception> public static string CatenateArrayItems(IXmpMeta xmp, string schemaNs, string arrayName, string separator, string quotes, bool allowCommas) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); ParameterAsserts.AssertImplementation(xmp); if (string.IsNullOrEmpty(separator)) { separator = "; "; } if (string.IsNullOrEmpty(quotes)) { quotes = "\""; } XmpMetaImpl xmpImpl = (XmpMetaImpl)xmp; // Return an empty result if the array does not exist, // hurl if it isn't the right form. XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName); XmpNode arrayNode = XmpNodeUtils.FindNode(xmpImpl.Root, arrayPath, false, null); if (arrayNode == null) { return(""); } if (!arrayNode.Options.Array || arrayNode.Options.ArrayAlternate) { throw new XmpException("Named property must be non-alternate array", XmpError.BADPARAM); } // Make sure the separator is OK. CheckSeparator(separator); // Make sure the open and close quotes are a legitimate pair. char openQuote = quotes[0]; char closeQuote = CheckQuotes(quotes, openQuote); // Build the result, quoting the array items, adding separators. // Hurl if any item isn't simple. StringBuilder catinatedString = new StringBuilder(); for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) { XmpNode currItem = (XmpNode)it.Current; if (currItem == null) { continue; } if (currItem.Options.CompositeProperty) { throw new XmpException("Array items must be simple", XmpError.BADPARAM); } string str = ApplyQuotes(currItem.Value, openQuote, closeQuote, allowCommas); catinatedString.Append(str); if (it.MoveNext()) { catinatedString.Append(separator); } } return(catinatedString.ToString()); }
/// <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; }
/// <summary> /// 7.2.10 nodeElementList<br> /// ws* ( nodeElement ws* )* /// /// Note: this method is only called from the rdf:RDF-node (top level) </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="rdfRdfNode"> the top-level xml node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfNodeElementList(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode rdfRdfNode) { for (int i = 0; i < rdfRdfNode.ChildNodes.Count; i++) { XmlNode child = rdfRdfNode.ChildNodes[i]; // filter whitespaces (and all text nodes) if (!IsWhitespaceNode(child)) { RdfNodeElement(xmp, xmpParent, child, true); } } }
/// <summary> /// Serializes the general qualifier. </summary> /// <param name="node"> the root node of the subtree </param> /// <param name="indent"> the current indent level </param> /// <exception cref="IOException"> Forwards all writer exceptions. </exception> /// <exception cref="XmpException"> If qualifier and element fields are mixed. </exception> private void SerializeCompactRdfGeneralQualifier(int indent, XmpNode node) { // The node has general qualifiers, ones that can't be // attributes on a property element. // Emit using the qualified property pseudo-struct form. The // value is output by a call // to SerializePrettyRDFProperty with emitAsRDFValue set. Write(" rdf:parseType=\"Resource\">"); WriteNewline(); SerializeCanonicalRdfProperty(node, false, true, indent + 1); for (IEnumerator iq = node.IterateQualifier(); iq.MoveNext();) { XmpNode qualifier = (XmpNode) iq.Current; if (qualifier == null) continue; SerializeCanonicalRdfProperty(qualifier, false, false, indent + 1); } }
/// /// <summary> /// 7.2.7 propertyAttributeURIs /// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) /// /// 7.2.11 nodeElement /// start-element ( URI == nodeElementURIs, /// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) /// propertyEltList /// end-element() /// /// Process the attribute list for an RDF node element. A property attribute URI is /// anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, /// as are rdf:about attributes on inner nodes. /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfNodeElementAttrs(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, bool isTopLevel) { // Used to detect attributes that are mutually exclusive. int exclusiveAttrs = 0; if (xmlNode == null || xmlNode.Attributes == null) return; for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes[i]; // quick hack, ns declarations do not appear in C++ // ignore "ID" without namespace if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } int attrTerm = GetRdfTermKind(attribute); switch (attrTerm) { case RDFTERM_ID: case RDFTERM_NODE_ID: case RDFTERM_ABOUT: if (exclusiveAttrs > 0) { throw new XmpException("Mutally exclusive about, ID, nodeID attributes", XmpError.BADRDF); } exclusiveAttrs++; if (isTopLevel && (attrTerm == RDFTERM_ABOUT)) { // This is the rdf:about attribute on a top level node. Set // the XMP tree name if // it doesn't have a name yet. Make sure this name matches // the XMP tree name. if (!string.IsNullOrEmpty(xmpParent.Name)) { if (!xmpParent.Name.Equals(attribute.Value)) { throw new XmpException("Mismatched top level rdf:about values", XmpError.BADXMP); } } else { xmpParent.Name = attribute.Value; } } break; case RDFTERM_OTHER: AddChildNode(xmp, xmpParent, attribute, attribute.Value, isTopLevel); break; default: throw new XmpException("Invalid nodeElement attribute", XmpError.BADRDF); } } }
/// <summary> /// Serializes one schema with all contained properties in pretty-printed /// manner.<br> /// Each schema's properties are written to a single /// rdf:Description element. All of the necessary namespaces are declared in /// the rdf:Description element. The baseIndent is the base level for the /// entire serialization, that of the x:xmpmeta element. An xml:lang /// qualifier is written as an attribute of the property start tag, not by /// itself forcing the qualified property form. /// /// <blockquote> /// /// <pre> /// <rdf:Description rdf:about="TreeName" xmlns:ns="URI" ... > /// /// ... The actual properties of the schema, see SerializePrettyRDFProperty /// /// <!-- ns1:Alias is aliased to ns2:Actual --> ... If alias comments are wanted /// /// </rdf:Description> /// </pre> /// /// </blockquote> /// </summary> /// <param name="schemaNode"> a schema node </param> /// <param name="level"> </param> /// <exception cref="IOException"> Forwarded writer exceptions </exception> /// <exception cref="XmpException"> </exception> private void SerializeCanonicalRdfSchema(XmpNode schemaNode, int level) { // Write each of the schema's actual properties. for (IEnumerator it = schemaNode.IterateChildren(); it.MoveNext();) { XmpNode propNode = (XmpNode) it.Current; if (propNode == null) continue; SerializeCanonicalRdfProperty(propNode, _options.UseCanonicalFormat, false, level + 2); } }
/// <summary> /// 7.2.14 propertyElt /// /// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | /// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | /// parseTypeOtherPropertyElt | emptyPropertyElt /// /// 7.2.15 resourcePropertyElt /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) /// ws* nodeElement ws* /// end-element() /// /// 7.2.16 literalPropertyElt /// start-element ( /// URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) /// text() /// end-element() /// /// 7.2.17 parseTypeLiteralPropertyElt /// start-element ( /// URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) /// literal /// end-element() /// /// 7.2.18 parseTypeResourcePropertyElt /// start-element ( /// URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) /// propertyEltList /// end-element() /// /// 7.2.19 parseTypeCollectionPropertyElt /// start-element ( /// URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) /// nodeElementList /// end-element() /// /// 7.2.20 parseTypeOtherPropertyElt /// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) /// propertyEltList /// end-element() /// /// 7.2.21 emptyPropertyElt /// start-element ( URI == propertyElementURIs, /// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) /// end-element() /// /// The various property element forms are not distinguished by the XML element name, /// but by their attributes for the most part. The exceptions are resourcePropertyElt and /// literalPropertyElt. They are distinguished by their XML element content. /// /// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can /// appear in many of these. We have to allow for it in the attibute counts below. /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfPropertyElement(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, bool isTopLevel) { int nodeTerm = GetRdfTermKind(xmlNode); if (!IsPropertyElementName(nodeTerm)) { throw new XmpException("Invalid property element name", XmpError.BADRDF); } // remove the namespace-definitions from the list XmlAttributeCollection attributes = xmlNode.Attributes; if (attributes == null) return; IList nsAttrs = null; for (int i = 0; i < attributes.Count; i++) { XmlNode attribute = attributes[i]; if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { if (nsAttrs == null) { nsAttrs = new ArrayList(); } nsAttrs.Add(attribute.Name); } } if (nsAttrs != null) { for (IEnumerator it = nsAttrs.GetEnumerator(); it.MoveNext();) { string ns = (string) it.Current; attributes.RemoveNamedItem(ns); } } if (attributes.Count > 3) { // Only an emptyPropertyElt can have more than 3 attributes. RdfEmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } else { // Look through the attributes for one that isn't rdf:ID or xml:lang, // it will usually tell what we should be dealing with. // The called routines must verify their specific syntax! for (int i = 0; i < attributes.Count; i++) { XmlNode attribute = attributes[i]; string attrLocal = attribute.LocalName; string attrNs = attribute.NamespaceURI; string attrValue = attribute.Value; if (!(XML_LANG.Equals(attribute.Name) && !("ID".Equals(attrLocal) && NS_RDF.Equals(attrNs)))) { if ("datatype".Equals(attrLocal) && NS_RDF.Equals(attrNs)) { RdfLiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } else if (!("parseType".Equals(attrLocal) && NS_RDF.Equals(attrNs))) { RdfEmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } else if ("Literal".Equals(attrValue)) { RdfParseTypeLiteralPropertyElement(); } else if ("Resource".Equals(attrValue)) { RdfParseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } else if ("Collection".Equals(attrValue)) { RdfParseTypeCollectionPropertyElement(); } else { RdfParseTypeOtherPropertyElement(); } return; } } // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, // or an emptyPropertyElt. Look at the child XML nodes to decide which. if (xmlNode.HasChildNodes) { for (int i = 0; i < xmlNode.ChildNodes.Count; i++) { XmlNode currChild = xmlNode.ChildNodes[i]; if (currChild.NodeType != XmlNodeType.Text) { RdfResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel); return; } } RdfLiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } else { RdfEmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel); } } }
/// <summary> /// Writes all used namespaces of the subtree in node to the output. /// The subtree is recursivly traversed. </summary> /// <param name="node"> the root node of the subtree </param> /// <param name="usedPrefixes"> a set containing currently used prefixes </param> /// <param name="indent"> the current indent level </param> /// <exception cref="IOException"> Forwards all writer exceptions. </exception> private void DeclareUsedNamespaces(XmpNode node, ISet usedPrefixes, int indent) { if (node.Options.SchemaNode) { // The schema node name is the URI, the value is the prefix. string prefix = node.Value.Substring(0, node.Value.Length - 1); DeclareNamespace(prefix, node.Name, usedPrefixes, indent); } else if (node.Options.Struct) { for (IEnumerator it = node.IterateChildren(); it.MoveNext();) { XmpNode field = (XmpNode) it.Current; if (field == null) continue; DeclareNamespace(field.Name, null, usedPrefixes, indent); } } for (IEnumerator it = node.IterateChildren(); it.MoveNext();) { XmpNode child = (XmpNode) it.Current; if (child == null) continue; DeclareUsedNamespaces(child, usedPrefixes, indent); } for (IEnumerator it = node.IterateQualifier(); it.MoveNext();) { XmpNode qualifier = (XmpNode) it.Current; if (qualifier == null) continue; DeclareNamespace(qualifier.Name, null, usedPrefixes, indent); DeclareUsedNamespaces(qualifier, usedPrefixes, indent); } }
/// <summary> /// 7.2.16 literalPropertyElt /// start-element ( URI == propertyElementURIs, /// attributes == set ( idAttr?, datatypeAttr?) ) /// text() /// end-element() /// /// Add a leaf node with the text value and qualifiers for the attributes. </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfLiteralPropertyElement(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, bool isTopLevel) { XmpNode newChild = AddChildNode(xmp, xmpParent, xmlNode, null, isTopLevel); if (xmlNode.Attributes != null) { for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes[i]; if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } string attrNs = attribute.NamespaceURI; string attrLocal = attribute.LocalName; if (XML_LANG.Equals(attribute.Name)) { AddQualifierNode(newChild, XML_LANG, attribute.Value); } else if (NS_RDF.Equals(attrNs) && ("ID".Equals(attrLocal) || "datatype".Equals(attrLocal))) { continue; // Ignore all rdf:ID and rdf:datatype attributes. } else throw new XmpException("Invalid attribute for literal property element", XmpError.BADRDF); } } string textValue = ""; for (int i = 0; i < xmlNode.ChildNodes.Count; i++) { XmlNode child = xmlNode.ChildNodes[i]; if (child.NodeType == XmlNodeType.Text) { textValue += child.Value; } else { throw new XmpException("Invalid child of literal property element", XmpError.BADRDF); } } newChild.Value = textValue; }
/// <summary> /// Start the outer rdf:Description element, including all needed xmlns attributes. /// Leave the element open so that the compact form can add property attributes. /// </summary> /// <exception cref="IOException"> If the writing to </exception> private void StartOuterRdfDescription(XmpNode schemaNode, int level) { WriteIndent(level + 1); Write(RDF_SCHEMA_START); WriteTreeName(); ISet usedPrefixes = new HashSet(); usedPrefixes.Add("xml"); usedPrefixes.Add("rdf"); DeclareUsedNamespaces(schemaNode, usedPrefixes, level + 3); Write('>'); WriteNewline(); }
/// <summary> /// 7.2.21 emptyPropertyElt /// start-element ( URI == propertyElementURIs, /// attributes == set ( /// idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) /// end-element() /// /// <ns:Prop1/> <!-- a simple property with an empty value --> /// <ns:Prop2 rdf:resource="http: *www.adobe.com/"/> <!-- a URI value --> /// <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property --> /// <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields --> /// /// An emptyPropertyElt is an element with no contained content, just a possibly empty set of /// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a /// simple property with an empty value (ns:Prop1), a simple property whose value is a URI /// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). /// An emptyPropertyElt can also represent an XMP struct whose fields are all simple and /// unqualified (ns:Prop4). /// /// It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the /// verbose form written using a literalPropertyElt. /// /// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for /// design reasons and partly for historical reasons. The XMP mapping rules are: /// <ol> /// <li> If there is an rdf:value attribute then this is a simple property /// with a text value. /// All other attributes are qualifiers. /// <li> If there is an rdf:resource attribute then this is a simple property /// with a URI value. /// All other attributes are qualifiers. /// <li> If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID /// then this is a simple /// property with an empty value. /// <li> Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, /// or rdf:nodeID are fields. /// </ol> /// </summary> /// <param name="xmp"> the xmp metadata object that is generated </param> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="xmlNode"> the currently processed XML node </param> /// <param name="isTopLevel"> Flag if the node is a top-level node </param> /// <exception cref="XmpException"> thown on parsing errors </exception> private static void RdfEmptyPropertyElement(XmpMetaImpl xmp, XmpNode xmpParent, XmlNode xmlNode, bool isTopLevel) { bool hasPropertyAttrs = false; bool hasResourceAttr = false; bool hasNodeIdAttr = false; bool hasValueAttr = false; XmlNode valueNode = null; // ! Can come from rdf:value or rdf:resource. if (xmlNode.HasChildNodes) { throw new XmpException("Nested content not allowed with rdf:resource or property attributes", XmpError.BADRDF); } // First figure out what XMP this maps to and remember the XML node for a simple value. if (xmlNode.Attributes != null) { for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes[i]; if ("xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; } int attrTerm = GetRdfTermKind(attribute); switch (attrTerm) { case RDFTERM_ID: // Nothing to do. break; case RDFTERM_RESOURCE: if (hasNodeIdAttr) { throw new XmpException( "Empty property element can't have both rdf:resource and rdf:nodeID", XmpError.BADRDF); } if (hasValueAttr) { throw new XmpException( "Empty property element can't have both rdf:value and rdf:resource", XmpError.BADXMP); } hasResourceAttr = true; if (!hasValueAttr) { valueNode = attribute; } break; case RDFTERM_NODE_ID: if (hasResourceAttr) { throw new XmpException( "Empty property element can't have both rdf:resource and rdf:nodeID", XmpError.BADRDF); } hasNodeIdAttr = true; break; case RDFTERM_OTHER: if ("value".Equals(attribute.LocalName) && NS_RDF.Equals(attribute.NamespaceURI)) { if (hasResourceAttr) { throw new XmpException( "Empty property element can't have both rdf:value and rdf:resource", XmpError.BADXMP); } hasValueAttr = true; valueNode = attribute; } else if (!XML_LANG.Equals(attribute.Name)) { hasPropertyAttrs = true; } break; default: throw new XmpException("Unrecognized attribute of empty property element", XmpError.BADRDF); } } } // Create the right kind of child node and visit the attributes again // to add the fields or qualifiers. // ! Because of implementation vagaries, // the xmpParent is the tree root for top level properties. // ! The schema is found, created if necessary, by addChildNode. XmpNode childNode = AddChildNode(xmp, xmpParent, xmlNode, "", isTopLevel); bool childIsStruct = false; if (hasValueAttr || hasResourceAttr) { childNode.Value = valueNode != null ? valueNode.Value : ""; if (!hasValueAttr) { // ! Might have both rdf:value and rdf:resource. childNode.Options.Uri = true; } } else if (hasPropertyAttrs) { childNode.Options.Struct = true; childIsStruct = true; } if (xmlNode.Attributes != null) { for (int i = 0; i < xmlNode.Attributes.Count; i++) { XmlNode attribute = xmlNode.Attributes[i]; if (attribute == valueNode || "xmlns".Equals(attribute.Prefix) || (attribute.Prefix == null && "xmlns".Equals(attribute.Name))) { continue; // Skip the rdf:value or rdf:resource attribute holding the value. } int attrTerm = GetRdfTermKind(attribute); switch (attrTerm) { case RDFTERM_ID: case RDFTERM_NODE_ID: break; // Ignore all rdf:ID and rdf:nodeID attributes. case RDFTERM_RESOURCE: AddQualifierNode(childNode, "rdf:resource", attribute.Value); break; case RDFTERM_OTHER: if (!childIsStruct) { AddQualifierNode(childNode, attribute.Name, attribute.Value); } else if (XML_LANG.Equals(attribute.Name)) { AddQualifierNode(childNode, XML_LANG, attribute.Value); } else { AddChildNode(xmp, childNode, attribute, attribute.Value, false); } break; default: throw new XmpException("Unrecognized attribute of empty property element", XmpError.BADRDF); } } } }
/// <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> /// <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty> /// /// <ns:UnqualifiedStructProperty> (If no rdf:resource qualifier) /// <rdf:Description> /// ... Fields, same forms as top level properties /// </rdf:Description> /// </ns:UnqualifiedStructProperty> /// /// <ns:ResourceStructProperty rdf:resource="URI" /// ... Fields as attributes /// > /// /// <ns:UnqualifiedArrayProperty> /// <rdf:Bag> or Seq or Alt /// ... Array items as rdf:li elements, same forms as top level properties /// </rdf:Bag> /// </ns:UnqualifiedArrayProperty> /// /// <ns:QualifiedProperty> /// <rdf:Description> /// <rdf:value> ... Property "value" following the unqualified /// forms ... </rdf:value> /// ... Qualifiers looking like named struct fields /// </rdf:Description> /// </ns:QualifiedProperty> /// </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="resource" attribute. </param> /// <param name="indent"> the current indent level </param> /// <exception cref="IOException"> Forwards all writer exceptions. </exception> /// <exception cref="XmpException"> If "rdf:resource" 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(); } }
/// <summary> /// Adds a qualifier node. /// </summary> /// <param name="xmpParent"> the parent xmp node </param> /// <param name="name"> the name of the qualifier which has to be /// QName including the <b>default prefix</b> </param> /// <param name="value"> the value of the qualifier </param> /// <returns> Returns the newly created child node. </returns> /// <exception cref="XmpException"> thown on parsing errors </exception> private static XmpNode AddQualifierNode(XmpNode xmpParent, string name, string value) { bool isLang = XML_LANG.Equals(name); // normalize value of language qualifiers XmpNode newQual = new XmpNode(name, isLang ? Utils.NormalizeLangValue(value) : value, null); xmpParent.AddQualifier(newQual); return newQual; }
/// <summary> /// Constructor for the node iterator. </summary> /// <param name="visitedNode"> the currently visited node </param> /// <param name="parentPath"> the accumulated path of the node </param> /// <param name="index"> the index within the parent node (only for arrays) </param> public NodeIterator(XmpIteratorImpl outerInstance, XmpNode visitedNode, string parentPath, int index) { _outerInstance = outerInstance; _visitedNode = visitedNode; _state = ITERATE_NODE; if (visitedNode.Options.SchemaNode) { outerInstance.BaseNs = visitedNode.Name; } // for all but the root node and schema nodes _path = AccumulatePath(visitedNode, parentPath, index); }
/// <summary> /// Evaluates a raw node value to the given value type, apply special /// conversions for defined types in XMP. /// </summary> /// <param name="valueType"> /// an int indicating the value type </param> /// <param name="propNode"> /// the node containing the value </param> /// <returns> Returns a literal value for the node. </returns> /// <exception cref="XmpException"> </exception> private object evaluateNodeValue(int valueType, XmpNode propNode) { object value; string rawValue = propNode.Value; switch (valueType) { case VALUE_BOOLEAN: value = XmpUtils.ConvertToBoolean(rawValue); break; case VALUE_INTEGER: value = XmpUtils.ConvertToInteger(rawValue); break; case VALUE_LONG: value = XmpUtils.ConvertToLong(rawValue); break; case VALUE_DOUBLE: value = XmpUtils.ConvertToDouble(rawValue); break; case VALUE_DATE: value = XmpUtils.ConvertToDate(rawValue); break; case VALUE_CALENDAR: IXmpDateTime dt = XmpUtils.ConvertToDate(rawValue); value = dt.Calendar; break; case VALUE_BASE64: value = XmpUtils.DecodeBase64(rawValue); break; default: // leaf values return empty string instead of null // for the other cases the converter methods provides a "null" // value. // a default value can only occur if this method is made public. value = rawValue != null || propNode.Options.CompositeProperty ? rawValue : ""; break; } return value; }
/// <param name="currNode"> the node that will be added to the path. </param> /// <param name="parentPath"> the path up to this node. </param> /// <param name="currentIndex"> the current array index if an arrey is traversed </param> /// <returns> Returns the updated path. </returns> protected internal virtual string AccumulatePath(XmpNode currNode, string parentPath, int currentIndex) { string separator; string segmentName; if (currNode.Parent == null || currNode.Options.SchemaNode) { return null; } if (currNode.Parent.Options.Array) { separator = ""; segmentName = "[" + Convert.ToString(currentIndex) + "]"; } else { separator = "/"; segmentName = currNode.Name; } if (String.IsNullOrEmpty(parentPath)) { return segmentName; } if (_outerInstance.Options.JustLeafname) { return !segmentName.StartsWith("?") ? segmentName : segmentName.Substring(1); // qualifier } return parentPath + separator + segmentName; }
public XmpPropertyImpl2(XmpNode propNode, object value) { _value = value; _propNode = propNode; }
/// <summary> /// Creates a property info object from an <code>XMPNode</code>. </summary> /// <param name="node"> an <code>XMPNode</code> </param> /// <param name="baseNs"> the base namespace to report </param> /// <param name="path"> the full property path </param> /// <returns> Returns a <code>XMPProperty</code>-object that serves representation of the node. </returns> protected internal virtual IXmpPropertyInfo CreatePropertyInfo(XmpNode node, string baseNs, string path) { string value = node.Options.SchemaNode ? null : node.Value; return new XmpPropertyInfoImpl(node, baseNs, path, value); }
/// <summary> /// Constructor for a cloned metadata tree. /// </summary> /// <param name="tree"> /// an prefilled metadata tree which fulfills all /// <code>XMPNode</code> contracts. </param> public XmpMetaImpl(XmpNode tree) { _tree = tree; }
public XmpPropertyInfoImpl(XmpNode node, string baseNs, string path, string value) { _node = node; _baseNs = baseNs; _path = path; _value = value; }
/// <summary> /// The internals for SetProperty() and related calls, used after the node is /// found or created. /// </summary> /// <param name="node"> /// the newly created node </param> /// <param name="value"> /// the node value, can be <code>null</code> </param> /// <param name="newOptions"> /// options for the new node, must not be <code>null</code>. </param> /// <param name="deleteExisting"> flag if the existing value is to be overwritten </param> /// <exception cref="XmpException"> thrown if options and value do not correspond </exception> internal virtual void SetNode(XmpNode node, object value, PropertyOptions newOptions, bool deleteExisting) { if (deleteExisting) { node.Clear(); } // its checked by setOptions(), if the merged result is a valid options set node.Options.MergeWith(newOptions); if (!node.Options.CompositeProperty) { // This is setting the value of a leaf node. XmpNodeUtils.SetNodeValue(node, value); } else { if (value != null && value.ToString().Length > 0) { throw new XmpException("Composite nodes can't have values", XmpError.BADXPATH); } node.RemoveChildren(); } }
/// <summary> /// Creates a property info object from an <code>XMPNode</code>. </summary> /// <param name="node"> an <code>XMPNode</code> </param> /// <param name="baseNs"> the base namespace to report </param> /// <param name="path"> the full property path </param> /// <returns> Returns a <code>XMPProperty</code>-object that serves representation of the node. </returns> protected internal virtual IXmpPropertyInfo CreatePropertyInfo(XmpNode node, string baseNs, string path) { string value = node.Options.SchemaNode ? null : node.Value; return(new XmpPropertyInfoImpl(node, baseNs, path, value)); }