/// <summary>Make sure the x-default item is first.</summary> /// <remarks> /// 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. /// </remarks> /// <param name="arrayNode">an alt text array node</param> internal static void NormalizeLangArray(XMPNode arrayNode) { if (!arrayNode.GetOptions().IsArrayAltText()) { return; } // check if node with x-default qual is first place for (int i = 2; i <= arrayNode.GetChildrenLength(); i++) { XMPNode child = arrayNode.GetChild(i); if (child.HasQualifier() && XMPConstConstants.XDefault.Equals(child.GetQualifier(1).GetValue())) { // move node to first place try { arrayNode.RemoveChild(i); arrayNode.AddChild(1, child); } catch (XMPException) { // cannot occur, because same child is removed before System.Diagnostics.Debug.Assert(false); } if (i == 2) { arrayNode.GetChild(2).SetValue(child.GetValue()); } break; } } }
/// <summary> /// The initial support for WAV files mapped a legacy ID3 audio copyright /// into a new xmpDM:copyright property. /// </summary> /// <remarks> /// 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> /// </remarks> /// <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).GetRoot(), XMPConstConstants.NsDc, true); string dmValue = dmCopyright.GetValue(); 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(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, dmValue, null); } else { int xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); if (xdIndex < 0) { // 2. No x-default item, create from the first item. string firstValue = dcRightsArray.GetChild(1).GetValue(); xmp.SetLocalizedText(XMPConstConstants.NsDc, "rights", string.Empty, XMPConstConstants.XDefault, firstValue, null); xdIndex = XMPNodeUtils.LookupLanguageItem(dcRightsArray, XMPConstConstants.XDefault); } // 3. Look for a double linefeed in the x-default value. XMPNode defaultNode = dcRightsArray.GetChild(xdIndex); string defaultValue = defaultNode.GetValue(); 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.SetValue(defaultValue + doubleLF + dmValue); } } else { // 3B. Has double LF, compare the tail. if (!Sharpen.Runtime.Substring(defaultValue, lfPos + 2).Equals(dmValue)) { // 3B2. Replace the x-default tail. defaultNode.SetValue(Sharpen.Runtime.Substring(defaultValue, 0, lfPos + 2) + dmValue); } } } // 4. Get rid of the xmpDM:copyright. dmCopyright.GetParent().RemoveChild(dmCopyright); } catch (XMPException) { } }
/// <summary> /// Searches for a field selector in a node: /// [fieldName="value] - an element in an array of structs, chosen by a field value. /// </summary> /// <remarks> /// 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. /// </remarks> /// <param name="arrayNode"/> /// <param name="fieldName"/> /// <param name="fieldValue"/> /// <returns>Returns the index of the field if found, otherwise -1.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static int LookupFieldSelector(XMPNode arrayNode, string fieldName, string fieldValue) { int result = -1; for (int index = 1; index <= arrayNode.GetChildrenLength() && result < 0; index++) { XMPNode currItem = arrayNode.GetChild(index); if (!currItem.GetOptions().IsStruct()) { throw new XMPException("Field selector must be used on array of struct", XMPErrorConstants.Badxpath); } for (int f = 1; f <= currItem.GetChildrenLength(); f++) { XMPNode currField = currItem.GetChild(f); if (!fieldName.Equals(currField.GetName())) { continue; } if (fieldValue.Equals(currField.GetValue())) { result = index; break; } } } return(result); }
/// <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. /// </summary> /// <remarks> /// 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>. /// </remarks> /// <param name="dcSchema">the DC schema node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if normalization fails</exception> private static void NormalizeDCArrays(XMPNode dcSchema) { for (int i = 1; i <= dcSchema.GetChildrenLength(); i++) { XMPNode currProp = dcSchema.GetChild(i); PropertyOptions arrayForm = (PropertyOptions)dcArrayForms.Get(currProp.GetName()); if (arrayForm == null) { continue; } else { if (currProp.GetOptions().IsSimple()) { // create a new array and add the current property as child, // if it was formerly simple XMPNode newArray = new XMPNode(currProp.GetName(), arrayForm); currProp.SetName(XMPConstConstants.ArrayItemName); newArray.AddChild(currProp); dcSchema.ReplaceChild(i, newArray); // fix language alternatives if (arrayForm.IsArrayAltText() && !currProp.GetOptions().GetHasLanguage()) { XMPNode newLang = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); currProp.AddQualifier(newLang); } } else { // clear array options and add corrected array form if it has been an array before currProp.GetOptions().SetOption(PropertyOptions.Array | PropertyOptions.ArrayOrdered | PropertyOptions.ArrayAlternate | PropertyOptions.ArrayAltText, false); currProp.GetOptions().MergeWith(arrayForm); if (arrayForm.IsArrayAltText()) { // applying for "dc:description", "dc:rights", "dc:title" RepairAltText(currProp); } } } } }
/// <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="Com.Adobe.Xmp.XMPException"/> internal static int LookupLanguageItem(XMPNode arrayNode, string language) { if (!arrayNode.GetOptions().IsArray()) { throw new XMPException("Language item must be used on array", XMPErrorConstants.Badxpath); } for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) { XMPNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XMPConstConstants.XmlLang.Equals(child.GetQualifier(1).GetName())) { continue; } else { if (language.Equals(child.GetQualifier(1).GetValue())) { return(index); } } } return(-1); }
/// <summary> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// </summary> /// <remarks> /// 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. /// </remarks> /// <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="Com.Adobe.Xmp.XMPException"></exception> private static int LookupQualSelector(XMPNode arrayNode, string qualName, string qualValue, int aliasForm) { if (XMPConstConstants.XmlLang.Equals(qualName)) { qualValue = Utils.NormalizeLangValue(qualValue); int index = Com.Adobe.Xmp.Impl.XMPNodeUtils.LookupLanguageItem(arrayNode, qualValue); if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0) { XMPNode langNode = new XMPNode(XMPConstConstants.ArrayItemName, null); XMPNode xdefault = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); langNode.AddQualifier(xdefault); arrayNode.AddChild(1, langNode); return(1); } else { return(index); } } else { for (int index = 1; index < arrayNode.GetChildrenLength(); index++) { XMPNode currItem = arrayNode.GetChild(index); for (Iterator it = currItem.IterateQualifier(); it.HasNext();) { XMPNode qualifier = (XMPNode)it.Next(); if (qualName.Equals(qualifier.GetName()) && qualValue.Equals(qualifier.GetValue())) { return(index); } } } return(-1); } }
/// <summary> /// After processing by ExpandXPath, a step can be of these forms: /// <ul> /// <li>qualName - A top level property or struct field. /// </summary> /// <remarks> /// 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. /// </remarks> /// <param name="parentNode">the node to start to start from</param> /// <param name="nextStep">the xpath segment</param> /// <param name="createNodes"></param> /// <returns>returns the found or created XMPPath node</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) { XMPNode nextNode = null; int index = 0; int stepKind = nextStep.GetKind(); if (stepKind == XMPPath.StructFieldStep) { nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.QualifierStep) { nextNode = FindQualifierNode(parentNode, Sharpen.Runtime.Substring(nextStep.GetName(), 1), createNodes); } else { // This is an array indexing step. First get the index, then get the node. if (!parentNode.GetOptions().IsArray()) { throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); } if (stepKind == XMPPath.ArrayIndexStep) { index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.ArrayLastStep) { index = parentNode.GetChildrenLength(); } else { if (stepKind == XMPPath.FieldSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string fieldName = result[0]; string fieldValue = result[1]; index = LookupFieldSelector(parentNode, fieldName, fieldValue); } else { if (stepKind == XMPPath.QualSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string qualName = result[0]; string qualValue = result[1]; index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); } else { throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); } } } } if (1 <= index && index <= parentNode.GetChildrenLength()) { nextNode = parentNode.GetChild(index); } } } return(nextNode); }
/// <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="Com.Adobe.Xmp.XMPException"/> internal static int LookupLanguageItem(XMPNode arrayNode, string language) { if (!arrayNode.GetOptions().IsArray()) { throw new XMPException("Language item must be used on array", XMPErrorConstants.Badxpath); } for (int index = 1; index <= arrayNode.GetChildrenLength(); index++) { XMPNode child = arrayNode.GetChild(index); if (!child.HasQualifier() || !XMPConstConstants.XmlLang.Equals(child.GetQualifier(1).GetName())) { continue; } else { if (language.Equals(child.GetQualifier(1).GetValue())) { return index; } } } return -1; }
/// <summary> /// <ol> /// <li>Look for an exact match with the specific language. /// </summary> /// <remarks> /// <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> /// </remarks> /// <param name="arrayNode">the alt text array node</param> /// <param name="genericLang">the generic language</param> /// <param name="specificLang">the specific language</param> /// <returns> /// Returns the kind of match as an Integer and the found node in an /// array. /// </returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> internal static object[] ChooseLocalizedText(XMPNode arrayNode, string genericLang, string specificLang) { // See if the array has the right form. Allow empty alt arrays, // that is what parsing returns. if (!arrayNode.GetOptions().IsArrayAltText()) { throw new XMPException("Localized text array is not alt-text", XMPErrorConstants.Badxpath); } else { if (!arrayNode.HasChildren()) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltNoValues, null }; } } int foundGenericMatches = 0; XMPNode resultNode = null; XMPNode xDefault = null; // Look for the first partial match with the generic language. for (Iterator it = arrayNode.IterateChildren(); it.HasNext(); ) { XMPNode currItem = (XMPNode)it.Next(); // perform some checks on the current item if (currItem.GetOptions().IsCompositeProperty()) { throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); } else { if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) { throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); } } string currLang = currItem.GetQualifier(1).GetValue(); // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSpecificMatch, currItem }; } else { if (genericLang != null && currLang.StartsWith(genericLang)) { if (resultNode == null) { resultNode = currItem; } // ! Don't return/break, need to look for other matches. foundGenericMatches++; } else { if (XMPConstConstants.XDefault.Equals(currLang)) { xDefault = currItem; } } } } // evaluate loop if (foundGenericMatches == 1) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSingleGeneric, resultNode }; } else { if (foundGenericMatches > 1) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltMultipleGeneric, resultNode }; } else { if (xDefault != null) { return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltXdefault, xDefault }; } else { // Everything failed, choose the first item. return new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltFirstItem, arrayNode.GetChild(1) }; } } } }
/// <summary> /// Searches for a qualifier selector in a node: /// [?qualName="value"] - an element in an array, chosen by a qualifier value. /// </summary> /// <remarks> /// 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. /// </remarks> /// <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="Com.Adobe.Xmp.XMPException"></exception> private static int LookupQualSelector(XMPNode arrayNode, string qualName, string qualValue, int aliasForm) { if (XMPConstConstants.XmlLang.Equals(qualName)) { qualValue = Utils.NormalizeLangValue(qualValue); int index = Com.Adobe.Xmp.Impl.XMPNodeUtils.LookupLanguageItem(arrayNode, qualValue); if (index < 0 && (aliasForm & AliasOptions.PropArrayAltText) > 0) { XMPNode langNode = new XMPNode(XMPConstConstants.ArrayItemName, null); XMPNode xdefault = new XMPNode(XMPConstConstants.XmlLang, XMPConstConstants.XDefault, null); langNode.AddQualifier(xdefault); arrayNode.AddChild(1, langNode); return 1; } else { return index; } } else { for (int index = 1; index < arrayNode.GetChildrenLength(); index++) { XMPNode currItem = arrayNode.GetChild(index); for (Iterator it = currItem.IterateQualifier(); it.HasNext(); ) { XMPNode qualifier = (XMPNode)it.Next(); if (qualName.Equals(qualifier.GetName()) && qualValue.Equals(qualifier.GetValue())) { 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. /// </summary> /// <remarks> /// 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. /// </remarks> /// <param name="arrayNode"/> /// <param name="fieldName"/> /// <param name="fieldValue"/> /// <returns>Returns the index of the field if found, otherwise -1.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static int LookupFieldSelector(XMPNode arrayNode, string fieldName, string fieldValue) { int result = -1; for (int index = 1; index <= arrayNode.GetChildrenLength() && result < 0; index++) { XMPNode currItem = arrayNode.GetChild(index); if (!currItem.GetOptions().IsStruct()) { throw new XMPException("Field selector must be used on array of struct", XMPErrorConstants.Badxpath); } for (int f = 1; f <= currItem.GetChildrenLength(); f++) { XMPNode currField = currItem.GetChild(f); if (!fieldName.Equals(currField.GetName())) { continue; } if (fieldValue.Equals(currField.GetValue())) { 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. /// </summary> /// <remarks> /// 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. /// </remarks> /// <param name="parentNode">the node to start to start from</param> /// <param name="nextStep">the xpath segment</param> /// <param name="createNodes"></param> /// <returns>returns the found or created XMPPath node</returns> /// <exception cref="Com.Adobe.Xmp.XMPException"></exception> private static XMPNode FollowXPathStep(XMPNode parentNode, XMPPathSegment nextStep, bool createNodes) { XMPNode nextNode = null; int index = 0; int stepKind = nextStep.GetKind(); if (stepKind == XMPPath.StructFieldStep) { nextNode = FindChildNode(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.QualifierStep) { nextNode = FindQualifierNode(parentNode, Sharpen.Runtime.Substring(nextStep.GetName(), 1), createNodes); } else { // This is an array indexing step. First get the index, then get the node. if (!parentNode.GetOptions().IsArray()) { throw new XMPException("Indexing applied to non-array", XMPErrorConstants.Badxpath); } if (stepKind == XMPPath.ArrayIndexStep) { index = FindIndexedItem(parentNode, nextStep.GetName(), createNodes); } else { if (stepKind == XMPPath.ArrayLastStep) { index = parentNode.GetChildrenLength(); } else { if (stepKind == XMPPath.FieldSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string fieldName = result[0]; string fieldValue = result[1]; index = LookupFieldSelector(parentNode, fieldName, fieldValue); } else { if (stepKind == XMPPath.QualSelectorStep) { string[] result = Utils.SplitNameAndValue(nextStep.GetName()); string qualName = result[0]; string qualValue = result[1]; index = LookupQualSelector(parentNode, qualName, qualValue, nextStep.GetAliasForm()); } else { throw new XMPException("Unknown array indexing step in FollowXPathStep", XMPErrorConstants.Internalfailure); } } } } if (1 <= index && index <= parentNode.GetChildrenLength()) { nextNode = parentNode.GetChild(index); } } } return nextNode; }
/// <summary> /// <ol> /// <li>Look for an exact match with the specific language. /// </summary> /// <remarks> /// <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> /// </remarks> /// <param name="arrayNode">the alt text array node</param> /// <param name="genericLang">the generic language</param> /// <param name="specificLang">the specific language</param> /// <returns> /// Returns the kind of match as an Integer and the found node in an /// array. /// </returns> /// <exception cref="Com.Adobe.Xmp.XMPException"/> internal static object[] ChooseLocalizedText(XMPNode arrayNode, string genericLang, string specificLang) { // See if the array has the right form. Allow empty alt arrays, // that is what parsing returns. if (!arrayNode.GetOptions().IsArrayAltText()) { throw new XMPException("Localized text array is not alt-text", XMPErrorConstants.Badxpath); } else { if (!arrayNode.HasChildren()) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltNoValues, null }); } } int foundGenericMatches = 0; XMPNode resultNode = null; XMPNode xDefault = null; // Look for the first partial match with the generic language. for (Iterator it = arrayNode.IterateChildren(); it.HasNext();) { XMPNode currItem = (XMPNode)it.Next(); // perform some checks on the current item if (currItem.GetOptions().IsCompositeProperty()) { throw new XMPException("Alt-text array item is not simple", XMPErrorConstants.Badxpath); } else { if (!currItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(currItem.GetQualifier(1).GetName())) { throw new XMPException("Alt-text array item has no language qualifier", XMPErrorConstants.Badxpath); } } string currLang = currItem.GetQualifier(1).GetValue(); // Look for an exact match with the specific language. if (specificLang.Equals(currLang)) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSpecificMatch, currItem }); } else { if (genericLang != null && currLang.StartsWith(genericLang)) { if (resultNode == null) { resultNode = currItem; } // ! Don't return/break, need to look for other matches. foundGenericMatches++; } else { if (XMPConstConstants.XDefault.Equals(currLang)) { xDefault = currItem; } } } } // evaluate loop if (foundGenericMatches == 1) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltSingleGeneric, resultNode }); } else { if (foundGenericMatches > 1) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltMultipleGeneric, resultNode }); } else { if (xDefault != null) { return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltXdefault, xDefault }); } else { // Everything failed, choose the first item. return(new object[] { Com.Adobe.Xmp.Impl.XMPNodeUtils.CltFirstItem, arrayNode.GetChild(1) }); } } } }
/// <summary> /// see /// <see cref="Com.Adobe.Xmp.XMPUtils.SeparateArrayItems(Com.Adobe.Xmp.XMPMeta, string, string, string, Com.Adobe.Xmp.Options.PropertyOptions, bool)"/> /// </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="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static void SeparateArrayItems(XMPMeta 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", XMPErrorConstants.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. string itemValue; int itemStart; int itemEnd; int nextKind = UckNormal; int charKind = UckNormal; char ch = (char)0; char nextChar = (char)0; itemEnd = 0; int endPos = catedStr.Length; while (itemEnd < endPos) { // 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 == UckNormal || charKind == UckQuote) { break; } } if (itemStart >= endPos) { break; } if (charKind != UckQuote) { // 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 == UckNormal || charKind == UckQuote || (charKind == UckComma && preserveCommas)) { continue; } else { if (charKind != UckSpace) { break; } else { if ((itemEnd + 1) < endPos) { ch = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(ch); if (nextKind == UckNormal || nextKind == UckQuote || (nextKind == UckComma && preserveCommas)) { continue; } } } } // Anything left? break; } // Have multiple spaces, or a space followed by a // separator. itemValue = Sharpen.Runtime.Substring(catedStr, itemStart, itemEnd); } 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 = string.Empty; for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind != UckQuote || !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. if ((itemEnd + 1) < endPos) { nextChar = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(nextChar); } else { nextKind = UckSemicolon; nextChar = (char)unchecked ((int)(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.GetChildrenLength(); oldChild++) { if (itemValue.Equals(arrayNode.GetChild(oldChild).GetValue())) { foundIndex = oldChild; break; } } XMPNode newItem = null; if (foundIndex < 0) { newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); arrayNode.AddChild(newItem); } } }
/// <summary>The parent is an RDF pseudo-struct containing an rdf:value field.</summary> /// <remarks> /// 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. /// </remarks> /// <param name="xmpParent">the parent xmp node</param> /// <exception cref="Com.Adobe.Xmp.XMPException">thown on parsing errors</exception> private static void FixupQualifiedNode(XMPNode xmpParent) { System.Diagnostics.Debug.Assert(xmpParent.GetOptions().IsStruct() && xmpParent.HasChildren()); XMPNode valueNode = xmpParent.GetChild(1); System.Diagnostics.Debug.Assert("rdf:value".Equals(valueNode.GetName())); // 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.GetOptions().GetHasLanguage()) { if (xmpParent.GetOptions().GetHasLanguage()) { throw new XMPException("Redundant xml:lang for rdf:value element", XMPErrorConstants.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.GetQualifierLength(); 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_1 = 2; i_1 <= xmpParent.GetChildrenLength(); i_1++) { XMPNode qualifier = xmpParent.GetChild(i_1); 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. System.Diagnostics.Debug.Assert(xmpParent.GetOptions().IsStruct() || xmpParent.GetHasValueChild()); xmpParent.SetHasValueChild(false); xmpParent.GetOptions().SetStruct(false); xmpParent.GetOptions().MergeWith(valueNode.GetOptions()); xmpParent.SetValue(valueNode.GetValue()); xmpParent.RemoveChildren(); for (Iterator it = valueNode.IterateChildren(); it.HasNext(); ) { XMPNode child = (XMPNode)it.Next(); xmpParent.AddChild(child); } }