/// <summary> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. <br> /// The <code>xml:lang</code> attribute was dropped from an /// <code>alt-text</code> item if the language was <code>x-default</code>. /// </summary> /// <param name="dcSchema"> the DC schema node </param> /// <exception cref="XmpException"> Thrown if normalization fails </exception> private static void NormalizeDcArrays(XmpNode dcSchema) { for (int i = 1; i <= dcSchema.ChildrenLength; i++) { XmpNode currProp = dcSchema.GetChild(i); PropertyOptions arrayForm = (PropertyOptions)_dcArrayForms[currProp.Name]; if (arrayForm == null) { continue; } if (currProp.Options.Simple) { // create a new array and add the current property as child, // if it was formerly simple XmpNode newArray = new XmpNode(currProp.Name, arrayForm); currProp.Name = XmpConst.ARRAY_ITEM_NAME; newArray.AddChild(currProp); dcSchema.ReplaceChild(i, newArray); // fix language alternatives if (arrayForm.ArrayAltText && !currProp.Options.HasLanguage) { XmpNode newLang = new XmpNode(XmpConst.XML_LANG, XmpConst.X_DEFAULT, null); currProp.AddQualifier(newLang); } } else { // clear array options and add corrected array form if it has been an array before currProp.Options.SetOption( PropertyOptions.ARRAY | PropertyOptions.ARRAY_ORDERED | PropertyOptions.ARRAY_ALTERNATE | PropertyOptions.ARRAY_ALT_TEXT, false); currProp.Options.MergeWith(arrayForm); if (arrayForm.ArrayAltText) { // applying for "dc:description", "dc:rights", "dc:title" RepairAltText(currProp); } } } }
/// <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> /// Looks for the appropriate language item in a text alternative array.item /// </summary> /// <param name="arrayNode"> /// an array node </param> /// <param name="language"> /// the requested language </param> /// <returns> Returns the index if the language has been found, -1 otherwise. </returns> /// <exception cref="XmpException"> </exception> internal static int LookupLanguageItem(XmpNode arrayNode, string language) { if (!arrayNode.Options.Array) { throw new XmpException("Language item must be used on array", XmpError.BADXPATH); } for (int index = 1; index <= arrayNode.ChildrenLength; index++) { XmpNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XML_LANG.Equals(child.GetQualifier(1).Name)) { continue; } if (language.Equals(child.GetQualifier(1).Value)) { return index; } } return -1; }
/// <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> /// Make sure the x-default item is first. Touch up "single value" /// arrays that have a default plus one real language. This case should have /// the same value for both items. Older Adobe apps were hardwired to only /// use the "x-default" item, so we copy that value to the other /// item. /// </summary> /// <param name="arrayNode"> /// an alt text array node </param> internal static void NormalizeLangArray(XmpNode arrayNode) { if (!arrayNode.Options.ArrayAltText) { return; } // check if node with x-default qual is first place for (int i = 2; i <= arrayNode.ChildrenLength; i++) { XmpNode child = arrayNode.GetChild(i); if (child.HasQualifier() && X_DEFAULT.Equals(child.GetQualifier(1).Value)) { // move node to first place try { arrayNode.RemoveChild(i); arrayNode.AddChild(1, child); } catch (XmpException) { // cannot occur, because same child is removed before Debug.Assert(false); } if (i == 2) { arrayNode.GetChild(2).Value = child.Value; } break; } } }
/// <summary> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// No implicit nodes are created for qualifier selectors, /// except for an alias to an x-default item. /// </summary> /// <param name="arrayNode"> an array node </param> /// <param name="qualName"> the qualifier name </param> /// <param name="qualValue"> the qualifier value </param> /// <param name="aliasForm"> in case the qual selector results from an alias, /// an x-default node is created if there has not been one. </param> /// <returns> Returns the index of th </returns> /// <exception cref="XmpException"> </exception> private static int LookupQualSelector(XmpNode arrayNode, string qualName, string qualValue, uint aliasForm) { if (XML_LANG.Equals(qualName)) { qualValue = Utils.NormalizeLangValue(qualValue); int index = LookupLanguageItem(arrayNode, qualValue); if (index < 0 && (aliasForm & AliasOptions.PROP_ARRAY_ALT_TEXT) > 0) { XmpNode langNode = new XmpNode(ARRAY_ITEM_NAME, null); XmpNode xdefault = new XmpNode(XML_LANG, X_DEFAULT, null); langNode.AddQualifier(xdefault); arrayNode.AddChild(1, langNode); return 1; } return index; } for (int index = 1; index < arrayNode.ChildrenLength; index++) { XmpNode currItem = arrayNode.GetChild(index); for (IEnumerator it = currItem.IterateQualifier(); it.MoveNext();) { XmpNode qualifier = (XmpNode) it.Current; if (qualifier != null && qualName.Equals(qualifier.Name) && qualValue.Equals(qualifier.Value)) { return index; } } } return -1; }
/// <summary> /// Searches for a field selector in a node: /// [fieldName="value] - an element in an array of structs, chosen by a field value. /// No implicit nodes are created by field selectors. /// </summary> /// <param name="arrayNode"> </param> /// <param name="fieldName"> </param> /// <param name="fieldValue"> </param> /// <returns> Returns the index of the field if found, otherwise -1. </returns> /// <exception cref="XmpException"> </exception> private static int LookupFieldSelector(XmpNode arrayNode, string fieldName, string fieldValue) { int result = -1; for (int index = 1; index <= arrayNode.ChildrenLength && result < 0; index++) { XmpNode currItem = arrayNode.GetChild(index); if (!currItem.Options.Struct) { throw new XmpException("Field selector must be used on array of struct", XmpError.BADXPATH); } for (int f = 1; f <= currItem.ChildrenLength; f++) { XmpNode currField = currItem.GetChild(f); if (!fieldName.Equals(currField.Name)) { continue; } if (fieldValue.Equals(currField.Value)) { result = index; break; } } } return result; }
/// <summary> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// <li>[index] - An element of an array. /// <li>[last()] - The last element of an array. /// <li>[qualName="value"] - An element in an array of structs, chosen by a field value. /// <li>[?qualName="value"] - An element in an array, chosen by a qualifier value. /// <li>?qualName - A general qualifier. /// </ul> /// Find the appropriate child node, resolving aliases, and optionally creating nodes. /// </summary> /// <param name="parentNode"> the node to start to start from </param> /// <param name="nextStep"> the xpath segment </param> /// <param name="createNodes"> </param> /// <returns> returns the found or created XmpPath node </returns> /// <exception cref="XmpException"> </exception> private static XmpNode FollowXPathStep(XmpNode parentNode, XmpPathSegment nextStep, bool createNodes) { XmpNode nextNode = null; uint stepKind = nextStep.Kind; if (stepKind == XmpPath.STRUCT_FIELD_STEP) { nextNode = FindChildNode(parentNode, nextStep.Name, createNodes); } else if (stepKind == XmpPath.QUALIFIER_STEP) { nextNode = FindQualifierNode(parentNode, nextStep.Name.Substring(1), createNodes); } else { // This is an array indexing step. First get the index, then get the node. int index; if (!parentNode.Options.Array) { throw new XmpException("Indexing applied to non-array", XmpError.BADXPATH); } if (stepKind == XmpPath.ARRAY_INDEX_STEP) { index = FindIndexedItem(parentNode, nextStep.Name, createNodes); } else if (stepKind == XmpPath.ARRAY_LAST_STEP) { index = parentNode.ChildrenLength; } else if (stepKind == XmpPath.FIELD_SELECTOR_STEP) { string[] result = Utils.SplitNameAndValue(nextStep.Name); string fieldName = result[0]; string fieldValue = result[1]; index = LookupFieldSelector(parentNode, fieldName, fieldValue); } else if (stepKind == XmpPath.QUAL_SELECTOR_STEP) { string[] result = Utils.SplitNameAndValue(nextStep.Name); string qualName = result[0]; string qualValue = result[1]; index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.AliasForm); } else { throw new XmpException("Unknown array indexing step in FollowXPathStep", XmpError.INTERNALFAILURE); } if (1 <= index && index <= parentNode.ChildrenLength) { nextNode = parentNode.GetChild(index); } } return nextNode; }
/// <summary> /// Undo the denormalization performed by the XMP used in Acrobat 5.<br> /// If a Dublin Core array had only one item, it was serialized as a simple /// property. <br> /// The <code>xml:lang</code> attribute was dropped from an /// <code>alt-text</code> item if the language was <code>x-default</code>. /// </summary> /// <param name="dcSchema"> the DC schema node </param> /// <exception cref="XmpException"> Thrown if normalization fails </exception> private static void NormalizeDcArrays(XmpNode dcSchema) { for (int i = 1; i <= dcSchema.ChildrenLength; i++) { XmpNode currProp = dcSchema.GetChild(i); PropertyOptions arrayForm = (PropertyOptions) _dcArrayForms[currProp.Name]; if (arrayForm == null) { continue; } if (currProp.Options.Simple) { // create a new array and add the current property as child, // if it was formerly simple XmpNode newArray = new XmpNode(currProp.Name, arrayForm); currProp.Name = XmpConst.ARRAY_ITEM_NAME; newArray.AddChild(currProp); dcSchema.ReplaceChild(i, newArray); // fix language alternatives if (arrayForm.ArrayAltText && !currProp.Options.HasLanguage) { XmpNode newLang = new XmpNode(XmpConst.XML_LANG, XmpConst.X_DEFAULT, null); currProp.AddQualifier(newLang); } } else { // clear array options and add corrected array form if it has been an array before currProp.Options.SetOption( PropertyOptions.ARRAY | PropertyOptions.ARRAY_ORDERED | PropertyOptions.ARRAY_ALTERNATE | PropertyOptions.ARRAY_ALT_TEXT, false); currProp.Options.MergeWith(arrayForm); if (arrayForm.ArrayAltText) { // applying for "dc:description", "dc:rights", "dc:title" RepairAltText(currProp); } } } }