/// <summary> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </summary> /// <param name="aliasNode"> the alias node </param> /// <param name="baseNode"> the base node of the alias </param> /// <param name="outerCall"> marks the outer call of the recursion </param> /// <exception cref="XmpException"> Forwards XMP errors </exception> private static void CompareAliasedSubtrees(XmpNode aliasNode, XmpNode baseNode, bool outerCall) { if (!aliasNode.Value.Equals(baseNode.Value) || aliasNode.ChildrenLength != baseNode.ChildrenLength) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } if (!outerCall && (!aliasNode.Name.Equals(baseNode.Name) || !aliasNode.Options.Equals(baseNode.Options) || aliasNode.QualifierLength != baseNode.QualifierLength)) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } for (IEnumerator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasChild = (XmpNode)an.Current; XmpNode baseChild = (XmpNode)bn.Current; CompareAliasedSubtrees(aliasChild, baseChild, false); } for (IEnumerator an = aliasNode.IterateQualifier(), bn = baseNode.IterateQualifier(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasQual = (XmpNode)an.Current; XmpNode baseQual = (XmpNode)bn.Current; CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <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(); }
/// <seealso cref= XMPUtils#appendProperties(XMPMeta, XMPMeta, boolean, boolean) </seealso> /// <param name="source"> The source XMP object. </param> /// <param name="destination"> The destination XMP object. </param> /// <param name="doAllProperties"> Do internal properties in addition to external properties. </param> /// <param name="replaceOldValues"> Replace the values of existing properties. </param> /// <param name="deleteEmptyValues"> Delete destination values if source property is empty. </param> /// <exception cref="XmpException"> Forwards the Exceptions from the metadata processing </exception> public static void AppendProperties(IXmpMeta source, IXmpMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) { ParameterAsserts.AssertImplementation(source); ParameterAsserts.AssertImplementation(destination); XmpMetaImpl src = (XmpMetaImpl)source; XmpMetaImpl dest = (XmpMetaImpl)destination; for (IEnumerator it = src.Root.IterateChildren(); it.MoveNext();) { XmpNode sourceSchema = (XmpNode)it.Current; if (sourceSchema == null) { continue; } // Make sure we have a destination schema node XmpNode destSchema = XmpNodeUtils.FindSchemaNode(dest.Root, sourceSchema.Name, false); bool createdSchema = false; if (destSchema == null) { PropertyOptions propertyOptions = new PropertyOptions(); propertyOptions.SchemaNode = true; destSchema = new XmpNode(sourceSchema.Name, sourceSchema.Value, propertyOptions); dest.Root.AddChild(destSchema); createdSchema = true; } // Process the source schema's children. for (IEnumerator ic = sourceSchema.IterateChildren(); ic.MoveNext();) { XmpNode sourceProp = (XmpNode)ic.Current; if (sourceProp == null) { continue; } if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.Name, sourceProp.Name)) { AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); } } if (!destSchema.HasChildren() && (createdSchema || deleteEmptyValues)) { // Don't create an empty schema / remove empty schema. dest.Root.RemoveChild(destSchema); } } }
/// <summary> /// Make sure that the array is well-formed AltText. Each item must be simple /// and have an "xml:lang" qualifier. If repairs are needed, keep simple /// non-empty items by adding the "xml:lang" with value "x-repair". </summary> /// <param name="arrayNode"> the property node of the array to repair. </param> /// <exception cref="XmpException"> Forwards unexpected exceptions. </exception> private static void RepairAltText(XmpNode arrayNode) { if (arrayNode == null || !arrayNode.Options.Array) { // Already OK or not even an array. return; } // fix options arrayNode.Options.ArrayOrdered = true; arrayNode.Options.ArrayAlternate = true; arrayNode.Options.ArrayAltText = true; ArrayList currChildsToRemove = new ArrayList(); IEnumerator it = arrayNode.IterateChildren(); while (it.MoveNext()) { XmpNode currChild = (XmpNode)it.Current; if (currChild == null) { continue; } if (currChild.Options.CompositeProperty) { // Delete non-simple children. currChildsToRemove.Add(currChild); } else if (!currChild.Options.HasLanguage) { string childValue = currChild.Value; if (String.IsNullOrEmpty(childValue)) { // Delete empty valued children that have no xml:lang. currChildsToRemove.Add(currChild); } else { // Add an xml:lang qualifier with the value "x-repair". XmpNode repairLang = new XmpNode(XmpConst.XML_LANG, "x-repair", null); currChild.AddQualifier(repairLang); } } } foreach (object o in currChildsToRemove) { arrayNode.Children.Remove(o); } }
/// <summary> /// See if an array is an alt-text array. If so, make sure the x-default item /// is first. /// </summary> /// <param name="arrayNode"> /// the array node to check if its an alt-text array </param> internal static void DetectAltText(XmpNode arrayNode) { if (arrayNode.Options.ArrayAlternate && arrayNode.HasChildren()) { bool isAltText = false; for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) { XmpNode child = (XmpNode)it.Current; if (child != null && child.Options != null && child.Options.HasLanguage) { isAltText = true; break; } } if (isAltText) { arrayNode.Options.ArrayAltText = true; NormalizeLangArray(arrayNode); } } }
/// <summary> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. Empty schemas are automatically remove /// by <code>XMPNode</code> /// </summary> /// <param name="schemaNode"> /// a schema node </param> /// <param name="doAllProperties"> /// flag if all properties or only externals shall be removed. </param> /// <returns> Returns true if the schema is empty after the operation. </returns> private static bool RemoveSchemaChildren(XmpNode schemaNode, bool doAllProperties) { ArrayList currPropsToRemove = new ArrayList(); for (IEnumerator it = schemaNode.IterateChildren(); it.MoveNext();) { XmpNode currProp = (XmpNode)it.Current; if (currProp == null) { continue; } if (doAllProperties || !Utils.IsInternalProperty(schemaNode.Name, currProp.Name)) { currPropsToRemove.Add(currProp); } } foreach (XmpNode xmpNode in currPropsToRemove) { schemaNode.Children.Remove(xmpNode); } currPropsToRemove.Clear(); return(!schemaNode.HasChildren()); }
/// <summary> /// Write each of the parent's simple unqualified properties as an attribute. Returns true if all /// of the properties are written as attributes. /// </summary> /// <param name="parentNode"> the parent property node </param> /// <param name="indent"> the current indent level </param> /// <returns> Returns true if all properties can be rendered as RDF attribute. </returns> /// <exception cref="IOException"> </exception> private bool SerializeCompactRdfAttrProps(XmpNode parentNode, int indent) { bool allAreAttrs = true; for (IEnumerator it = parentNode.IterateChildren(); it.MoveNext();) { XmpNode prop = (XmpNode) it.Current; if (prop != null && canBeRDFAttrProp(prop)) { WriteNewline(); WriteIndent(indent); Write(prop.Name); Write("=\""); AppendNodeValue(prop.Value, true); Write('\"'); } else { allAreAttrs = false; } } return allAreAttrs; }
/// <summary> /// Compares two nodes including its children and qualifier. </summary> /// <param name="leftNode"> an <code>XMPNode</code> </param> /// <param name="rightNode"> an <code>XMPNode</code> </param> /// <returns> Returns true if the nodes are equal, false otherwise. </returns> /// <exception cref="XmpException"> Forwards exceptions to the calling method. </exception> private static bool ItemValuesMatch(XmpNode leftNode, XmpNode rightNode) { PropertyOptions leftForm = leftNode.Options; PropertyOptions rightForm = rightNode.Options; if (leftForm.Equals(rightForm)) { return false; } if (leftForm.Options == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.Value.Equals(rightNode.Value)) { return false; } if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage) { return false; } if (leftNode.Options.HasLanguage && !leftNode.GetQualifier(1).Value.Equals(rightNode.GetQualifier(1).Value)) { return false; } } else if (leftForm.Struct) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.ChildrenLength != rightNode.ChildrenLength) { return false; } for (IEnumerator it = leftNode.IterateChildren(); it.MoveNext();) { XmpNode leftField = (XmpNode) it.Current; if (leftField == null) continue; XmpNode rightField = XmpNodeUtils.FindChildNode(rightNode, leftField.Name, false); if (rightField == null || !ItemValuesMatch(leftField, rightField)) { return false; } } } else { // Array nodes, see if the "leftNode" values are present in the // "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the // destination for AppendProperties. Debug.Assert(leftForm.Array); for (IEnumerator il = leftNode.IterateChildren(); il.MoveNext();) { XmpNode leftItem = (XmpNode) il.Current; if (leftItem == null) continue; bool match = false; for (IEnumerator ir = rightNode.IterateChildren(); ir.MoveNext();) { XmpNode rightItem = (XmpNode) ir.Current; if (rightItem == null) continue; if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return false; } } } return true; // All of the checks passed. }
/// <seealso cref= XMPUtilsImpl#appendProperties(XMPMeta, XMPMeta, boolean, boolean, boolean) </seealso> /// <param name="destXmp"> The destination XMP object. </param> /// <param name="sourceNode"> the source node </param> /// <param name="destParent"> the parent of the destination node </param> /// <param name="replaceOldValues"> Replace the values of existing properties. </param> /// <param name="deleteEmptyValues"> flag if properties with empty values should be deleted /// in the destination object. </param> /// <exception cref="XmpException"> </exception> private static void AppendSubtree(XmpMetaImpl destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XmpNode destNode = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.Options.Simple ? string.IsNullOrEmpty(sourceNode.Value) : !sourceNode.HasChildren(); } if (deleteEmptyValues && valueIsEmpty) { if (destNode != null) { destParent.RemoveChild(destNode); } } else if (destNode == null) { // The one easy case, the destination does not exist. destParent.AddChild((XmpNode) sourceNode.Clone()); } else if (replaceOldValues) { // The destination exists and should be replaced. destXmp.SetNode(destNode, sourceNode.Value, sourceNode.Options, true); destParent.RemoveChild(destNode); destNode = (XmpNode) sourceNode.Clone(); destParent.AddChild(destNode); } else { // The destination exists and is not totally replaced. Structs and // arrays are merged. PropertyOptions sourceForm = sourceNode.Options; PropertyOptions destForm = destNode.Options; if (sourceForm != destForm) { return; } if (sourceForm.Struct) { // To merge a struct process the fields recursively. E.g. add simple missing fields. // The recursive call to AppendSubtree will handle deletion for fields with empty // values. for (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceField = (XmpNode) it.Current; if (sourceField == null) continue; AppendSubtree(destXmp, sourceField, destNode, replaceOldValues, deleteEmptyValues); if (deleteEmptyValues && !destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else if (sourceForm.ArrayAltText) { // Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. // Make a special check for deletion of empty values. Meaningful in AltText arrays // because the "xml:lang" qualifier provides unambiguous source/dest correspondence. for (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceItem = (XmpNode) it.Current; if (sourceItem == null) continue; if (!sourceItem.HasQualifier() || !XML_LANG.Equals(sourceItem.GetQualifier(1).Name)) { continue; } int destIndex = XmpNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).Value); if (deleteEmptyValues && (string.IsNullOrEmpty(sourceItem.Value))) { if (destIndex != -1) { destNode.RemoveChild(destIndex); if (!destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else if (destIndex == -1) { // Not replacing, keep the existing item. if (!X_DEFAULT.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XmpNode destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options); sourceItem.CloneSubtree(destItem); destNode.AddChild(1, destItem); } } } } else if (sourceForm.Array) { // Merge other arrays by item values. Don't worry about order or duplicates. Source // items with empty values do not cause deletion, that conflicts horribly with // merging. for (IEnumerator @is = sourceNode.IterateChildren(); @is.MoveNext();) { XmpNode sourceItem = (XmpNode) @is.Current; if (sourceItem == null) continue; bool match = false; for (IEnumerator id = destNode.IterateChildren(); id.MoveNext();) { XmpNode destItem = (XmpNode) id.Current; if (destItem == null) continue; if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XmpNode) sourceItem.Clone(); destParent.AddChild(destNode); } } } } }
/// <summary> /// Remove all schema children according to the flag /// <code>doAllProperties</code>. Empty schemas are automatically remove /// by <code>XMPNode</code> /// </summary> /// <param name="schemaNode"> /// a schema node </param> /// <param name="doAllProperties"> /// flag if all properties or only externals shall be removed. </param> /// <returns> Returns true if the schema is empty after the operation. </returns> private static bool RemoveSchemaChildren(XmpNode schemaNode, bool doAllProperties) { ArrayList currPropsToRemove = new ArrayList(); for (IEnumerator it = schemaNode.IterateChildren(); it.MoveNext();) { XmpNode currProp = (XmpNode) it.Current; if (currProp == null) continue; if (doAllProperties || !Utils.IsInternalProperty(schemaNode.Name, currProp.Name)) { currPropsToRemove.Add(currProp); } } foreach (XmpNode xmpNode in currPropsToRemove) { schemaNode.Children.Remove(xmpNode); } currPropsToRemove.Clear(); return !schemaNode.HasChildren(); }
/// <summary> /// <ol> /// <li>Look for an exact match with the specific language. /// <li>If a generic language is given, look for partial matches. /// <li>Look for an "x-default"-item. /// <li>Choose the first item. /// </ol> /// </summary> /// <param name="arrayNode"> /// the alt text array node </param> /// <param name="genericLang"> /// the generic language </param> /// <param name="specificLang"> /// the specific language </param> /// <returns> Returns the kind of match as an Integer and the found node in an /// array. /// </returns> /// <exception cref="XmpException"> </exception> internal static object[] ChooseLocalizedText(XmpNode arrayNode, string genericLang, string specificLang) { // See if the array has the right form. Allow empty alt arrays, // that is what parsing returns. if (!arrayNode.Options.ArrayAltText) { throw new XmpException("Localized text array is not alt-text", XmpError.BADXPATH); } if (!arrayNode.HasChildren()) { return new object[] {CLT_NO_VALUES, null}; } int foundGenericMatches = 0; XmpNode resultNode = null; XmpNode xDefault = null; // Look for the first partial match with the generic language. for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) { XmpNode currItem = (XmpNode) it.Current; // perform some checks on the current item if (currItem == null || currItem.Options == null || currItem.Options.CompositeProperty) { throw new XmpException("Alt-text array item is not simple", XmpError.BADXPATH); } if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name)) { throw new XmpException("Alt-text array item has no language qualifier", XmpError.BADXPATH); } string currLang = currItem.GetQualifier(1).Value; // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return new object[] {CLT_SPECIFIC_MATCH, currItem}; } if (genericLang != null && currLang.StartsWith(genericLang)) { if (resultNode == null) { resultNode = currItem; } // ! Don't return/break, need to look for other matches. foundGenericMatches++; } else if (X_DEFAULT.Equals(currLang)) { xDefault = currItem; } } // evaluate loop if (foundGenericMatches == 1) { return new object[] {CLT_SINGLE_GENERIC, resultNode}; } if (foundGenericMatches > 1) { return new object[] {CLT_MULTIPLE_GENERIC, resultNode}; } if (xDefault != null) { return new object[] {CLT_XDEFAULT, xDefault}; } { // Everything failed, choose the first item. return new object[] {CLT_FIRST_ITEM, arrayNode.GetChild(1)}; } }
/// <summary> /// See if an array is an alt-text array. If so, make sure the x-default item /// is first. /// </summary> /// <param name="arrayNode"> /// the array node to check if its an alt-text array </param> internal static void DetectAltText(XmpNode arrayNode) { if (arrayNode.Options.ArrayAlternate && arrayNode.HasChildren()) { bool isAltText = false; for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) { XmpNode child = (XmpNode) it.Current; if (child != null && child.Options != null && child.Options.HasLanguage) { isAltText = true; break; } } if (isAltText) { arrayNode.Options.ArrayAltText = true; NormalizeLangArray(arrayNode); } } }
/// <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; } }
/// <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> /// 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> /// 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 bwlow ignore attribute qualifiers such as /// xml:lang, they don't affect the output form. /// /// <blockquote> /// /// <pre> /// <ns:UnqualifiedStructProperty-1 /// ... The fields as attributes, if all are simple and unqualified /// /> /// /// <ns:UnqualifiedStructProperty-2 rdf:parseType="Resource"> /// ... The fields as elements, if none are simple and unqualified /// </ns:UnqualifiedStructProperty-2> /// /// <ns:UnqualifiedStructProperty-3> /// <rdf:Description /// ... The simple and unqualified fields as attributes /// > /// ... The compound or qualified fields as elements /// </rdf:Description> /// </ns:UnqualifiedStructProperty-3> /// /// <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:parseType="Resource"> /// <rdf:value> ... Property "value" /// following the unqualified forms ... </rdf:value> /// ... Qualifiers looking like named struct fields /// </ns:QualifiedProperty> /// </pre> /// /// </blockquote> /// /// *** Consider numbered array items, but has compatibility problems. *** /// Consider qualified form with rdf:Description and attributes. /// </summary> /// <param name="parentNode"> the parent node </param> /// <param name="indent"> the current indent level </param> /// <exception cref="IOException"> Forwards writer exceptions </exception> /// <exception cref="XmpException"> If qualifier and element fields are mixed. </exception> private void SerializeCompactRdfElementProps(XmpNode parentNode, int indent) { for (IEnumerator it = parentNode.IterateChildren(); it.MoveNext();) { XmpNode node = (XmpNode) it.Current; if (node == null) continue; if (canBeRDFAttrProp(node)) { continue; } bool emitEndTag = true; bool 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. string elemName = node.Name; if (XmpConst.ARRAY_ITEM_NAME.Equals(elemName)) { elemName = "rdf:li"; } WriteIndent(indent); Write('<'); Write(elemName); bool hasGeneralQualifiers = false; bool hasRdfResourceQual = false; for (IEnumerator iq = node.IterateQualifier(); iq.MoveNext();) { XmpNode qualifier = (XmpNode) iq.Current; if (qualifier == null) continue; if (!RDF_ATTR_QUALIFIER.Contains(qualifier.Name)) { hasGeneralQualifiers = true; } else { hasRdfResourceQual = "rdf:resource".Equals(qualifier.Name); 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.CompositeProperty) { object[] result = SerializeCompactRdfSimpleProp(node); emitEndTag = (bool) ((bool?) result[0]); indentEndTag = (bool) ((bool?) result[1]); } else if (node.Options.Array) { 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(); } } }
/// <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> /// Compares two nodes including its children and qualifier. </summary> /// <param name="leftNode"> an <code>XMPNode</code> </param> /// <param name="rightNode"> an <code>XMPNode</code> </param> /// <returns> Returns true if the nodes are equal, false otherwise. </returns> /// <exception cref="XmpException"> Forwards exceptions to the calling method. </exception> private static bool ItemValuesMatch(XmpNode leftNode, XmpNode rightNode) { PropertyOptions leftForm = leftNode.Options; PropertyOptions rightForm = rightNode.Options; if (leftForm.Equals(rightForm)) { return(false); } if (leftForm.Options == 0) { // Simple nodes, check the values and xml:lang qualifiers. if (!leftNode.Value.Equals(rightNode.Value)) { return(false); } if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage) { return(false); } if (leftNode.Options.HasLanguage && !leftNode.GetQualifier(1).Value.Equals(rightNode.GetQualifier(1).Value)) { return(false); } } else if (leftForm.Struct) { // Struct nodes, see if all fields match, ignoring order. if (leftNode.ChildrenLength != rightNode.ChildrenLength) { return(false); } for (IEnumerator it = leftNode.IterateChildren(); it.MoveNext();) { XmpNode leftField = (XmpNode)it.Current; if (leftField == null) { continue; } XmpNode rightField = XmpNodeUtils.FindChildNode(rightNode, leftField.Name, false); if (rightField == null || !ItemValuesMatch(leftField, rightField)) { return(false); } } } else { // Array nodes, see if the "leftNode" values are present in the // "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the // destination for AppendProperties. Debug.Assert(leftForm.Array); for (IEnumerator il = leftNode.IterateChildren(); il.MoveNext();) { XmpNode leftItem = (XmpNode)il.Current; if (leftItem == null) { continue; } bool match = false; for (IEnumerator ir = rightNode.IterateChildren(); ir.MoveNext();) { XmpNode rightItem = (XmpNode)ir.Current; if (rightItem == null) { continue; } if (ItemValuesMatch(leftItem, rightItem)) { match = true; break; } } if (!match) { return(false); } } } return(true); // All of the checks passed. }
/// <seealso cref= XMPUtilsImpl#appendProperties(XMPMeta, XMPMeta, boolean, boolean, boolean) </seealso> /// <param name="destXmp"> The destination XMP object. </param> /// <param name="sourceNode"> the source node </param> /// <param name="destParent"> the parent of the destination node </param> /// <param name="replaceOldValues"> Replace the values of existing properties. </param> /// <param name="deleteEmptyValues"> flag if properties with empty values should be deleted /// in the destination object. </param> /// <exception cref="XmpException"> </exception> private static void AppendSubtree(XmpMetaImpl destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XmpNode destNode = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.Options.Simple ? string.IsNullOrEmpty(sourceNode.Value) : !sourceNode.HasChildren(); } if (deleteEmptyValues && valueIsEmpty) { if (destNode != null) { destParent.RemoveChild(destNode); } } else if (destNode == null) { // The one easy case, the destination does not exist. destParent.AddChild((XmpNode)sourceNode.Clone()); } else if (replaceOldValues) { // The destination exists and should be replaced. destXmp.SetNode(destNode, sourceNode.Value, sourceNode.Options, true); destParent.RemoveChild(destNode); destNode = (XmpNode)sourceNode.Clone(); destParent.AddChild(destNode); } else { // The destination exists and is not totally replaced. Structs and // arrays are merged. PropertyOptions sourceForm = sourceNode.Options; PropertyOptions destForm = destNode.Options; if (sourceForm != destForm) { return; } if (sourceForm.Struct) { // To merge a struct process the fields recursively. E.g. add simple missing fields. // The recursive call to AppendSubtree will handle deletion for fields with empty // values. for (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceField = (XmpNode)it.Current; if (sourceField == null) { continue; } AppendSubtree(destXmp, sourceField, destNode, replaceOldValues, deleteEmptyValues); if (deleteEmptyValues && !destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else if (sourceForm.ArrayAltText) { // Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. // Make a special check for deletion of empty values. Meaningful in AltText arrays // because the "xml:lang" qualifier provides unambiguous source/dest correspondence. for (IEnumerator it = sourceNode.IterateChildren(); it.MoveNext();) { XmpNode sourceItem = (XmpNode)it.Current; if (sourceItem == null) { continue; } if (!sourceItem.HasQualifier() || !XML_LANG.Equals(sourceItem.GetQualifier(1).Name)) { continue; } int destIndex = XmpNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).Value); if (deleteEmptyValues && (string.IsNullOrEmpty(sourceItem.Value))) { if (destIndex != -1) { destNode.RemoveChild(destIndex); if (!destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else if (destIndex == -1) { // Not replacing, keep the existing item. if (!X_DEFAULT.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XmpNode destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options); sourceItem.CloneSubtree(destItem); destNode.AddChild(1, destItem); } } } } else if (sourceForm.Array) { // Merge other arrays by item values. Don't worry about order or duplicates. Source // items with empty values do not cause deletion, that conflicts horribly with // merging. for (IEnumerator @is = sourceNode.IterateChildren(); @is.MoveNext();) { XmpNode sourceItem = (XmpNode)@is.Current; if (sourceItem == null) { continue; } bool match = false; for (IEnumerator id = destNode.IterateChildren(); id.MoveNext();) { XmpNode destItem = (XmpNode)id.Current; if (destItem == null) { continue; } if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XmpNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } }
/// <summary> /// 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> /// <ol> /// <li>Look for an exact match with the specific language. /// <li>If a generic language is given, look for partial matches. /// <li>Look for an "x-default"-item. /// <li>Choose the first item. /// </ol> /// </summary> /// <param name="arrayNode"> /// the alt text array node </param> /// <param name="genericLang"> /// the generic language </param> /// <param name="specificLang"> /// the specific language </param> /// <returns> Returns the kind of match as an Integer and the found node in an /// array. /// </returns> /// <exception cref="XmpException"> </exception> internal static object[] ChooseLocalizedText(XmpNode arrayNode, string genericLang, string specificLang) { // See if the array has the right form. Allow empty alt arrays, // that is what parsing returns. if (!arrayNode.Options.ArrayAltText) { throw new XmpException("Localized text array is not alt-text", XmpError.BADXPATH); } if (!arrayNode.HasChildren()) { return(new object[] { CLT_NO_VALUES, null }); } int foundGenericMatches = 0; XmpNode resultNode = null; XmpNode xDefault = null; // Look for the first partial match with the generic language. for (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) { XmpNode currItem = (XmpNode)it.Current; // perform some checks on the current item if (currItem == null || currItem.Options == null || currItem.Options.CompositeProperty) { throw new XmpException("Alt-text array item is not simple", XmpError.BADXPATH); } if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name)) { throw new XmpException("Alt-text array item has no language qualifier", XmpError.BADXPATH); } string currLang = currItem.GetQualifier(1).Value; // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return(new object[] { CLT_SPECIFIC_MATCH, currItem }); } if (genericLang != null && currLang.StartsWith(genericLang)) { if (resultNode == null) { resultNode = currItem; } // ! Don't return/break, need to look for other matches. foundGenericMatches++; } else if (X_DEFAULT.Equals(currLang)) { xDefault = currItem; } } // evaluate loop if (foundGenericMatches == 1) { return(new object[] { CLT_SINGLE_GENERIC, resultNode }); } if (foundGenericMatches > 1) { return(new object[] { CLT_MULTIPLE_GENERIC, resultNode }); } if (xDefault != null) { return(new object[] { CLT_XDEFAULT, xDefault }); } { // Everything failed, choose the first item. return(new object[] { CLT_FIRST_ITEM, arrayNode.GetChild(1) }); } }
/// <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> /// Make sure that the array is well-formed AltText. Each item must be simple /// and have an "xml:lang" qualifier. If repairs are needed, keep simple /// non-empty items by adding the "xml:lang" with value "x-repair". </summary> /// <param name="arrayNode"> the property node of the array to repair. </param> /// <exception cref="XmpException"> Forwards unexpected exceptions. </exception> private static void RepairAltText(XmpNode arrayNode) { if (arrayNode == null || !arrayNode.Options.Array) { // Already OK or not even an array. return; } // fix options arrayNode.Options.ArrayOrdered = true; arrayNode.Options.ArrayAlternate = true; arrayNode.Options.ArrayAltText = true; ArrayList currChildsToRemove = new ArrayList(); IEnumerator it = arrayNode.IterateChildren(); while (it.MoveNext()) { XmpNode currChild = (XmpNode) it.Current; if (currChild == null) continue; if (currChild.Options.CompositeProperty) { // Delete non-simple children. currChildsToRemove.Add(currChild); } else if (!currChild.Options.HasLanguage) { string childValue = currChild.Value; if (String.IsNullOrEmpty(childValue)) { // Delete empty valued children that have no xml:lang. currChildsToRemove.Add(currChild); } else { // Add an xml:lang qualifier with the value "x-repair". XmpNode repairLang = new XmpNode(XmpConst.XML_LANG, "x-repair", null); currChild.AddQualifier(repairLang); } } } foreach (object o in currChildsToRemove) { arrayNode.Children.Remove(o); } }
/// <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> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </summary> /// <param name="aliasNode"> the alias node </param> /// <param name="baseNode"> the base node of the alias </param> /// <param name="outerCall"> marks the outer call of the recursion </param> /// <exception cref="XmpException"> Forwards XMP errors </exception> private static void CompareAliasedSubtrees(XmpNode aliasNode, XmpNode baseNode, bool outerCall) { if (!aliasNode.Value.Equals(baseNode.Value) || aliasNode.ChildrenLength != baseNode.ChildrenLength) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } if (!outerCall && (!aliasNode.Name.Equals(baseNode.Name) || !aliasNode.Options.Equals(baseNode.Options) || aliasNode.QualifierLength != baseNode.QualifierLength)) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } for (IEnumerator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasChild = (XmpNode) an.Current; XmpNode baseChild = (XmpNode) bn.Current; CompareAliasedSubtrees(aliasChild, baseChild, false); } for (IEnumerator an = aliasNode.IterateQualifier(), bn = baseNode.IterateQualifier(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasQual = (XmpNode) an.Current; XmpNode baseQual = (XmpNode) bn.Current; CompareAliasedSubtrees(aliasQual, baseQual, false); } }