/** * Parses the root node of an XMP Path, checks if namespace and prefix fit together * and resolve the property to the base property if it is an alias. * @param schemaNs the root namespace * @param pos the parsing position helper * @param expandedXPath the path to contribute to * @throws XmpException If the path is not valid. */ internal static void ParseRootNode(string schemaNs, PathPosition pos, XmpPath expandedXPath) { while (pos.StepEnd < pos.Path.Length && "/[*".IndexOf(pos.Path[pos.StepEnd]) < 0) { pos.StepEnd++; } if (pos.StepEnd == pos.StepBegin) { throw new XmpException("Empty initial XmpPath step", XmpError.BADXPATH); } string rootProp = VerifyXPathRoot(schemaNs, pos.Path.Substring(pos.StepBegin, pos.StepEnd - pos.StepBegin)); IXmpAliasInfo aliasInfo = XmpMetaFactory.SchemaRegistry.FindAlias(rootProp); if (aliasInfo == null) { // add schema xpath step expandedXPath.Add(new XmpPathSegment(schemaNs, XmpPath.SCHEMA_NODE)); XmpPathSegment rootStep = new XmpPathSegment(rootProp, XmpPath.STRUCT_FIELD_STEP); expandedXPath.Add(rootStep); } else { // add schema xpath step and base step of alias expandedXPath.Add(new XmpPathSegment(aliasInfo.Namespace, XmpPath.SCHEMA_NODE)); XmpPathSegment rootStep = new XmpPathSegment(VerifyXPathRoot(aliasInfo.Namespace, aliasInfo.PropName), XmpPath.STRUCT_FIELD_STEP); rootStep.Alias = true; rootStep.AliasForm = aliasInfo.AliasForm.Options; expandedXPath.Add(rootStep); if (aliasInfo.AliasForm.ArrayAltText) { XmpPathSegment qualSelectorStep = new XmpPathSegment("[?xml:lang='x-default']", XmpPath.QUAL_SELECTOR_STEP); qualSelectorStep.Alias = true; qualSelectorStep.AliasForm = aliasInfo.AliasForm.Options; expandedXPath.Add(qualSelectorStep); } else if (aliasInfo.AliasForm.Array) { XmpPathSegment indexStep = new XmpPathSegment("[1]", XmpPath.ARRAY_INDEX_STEP); indexStep.Alias = true; indexStep.AliasForm = aliasInfo.AliasForm.Options; expandedXPath.Add(indexStep); } } }
/// <summary> /// Constructor with optionsl initial values. If <code>propName</code> is provided, /// <code>schemaNs</code> has also be provided. </summary> /// <param name="xmp"> the iterated metadata object. </param> /// <param name="schemaNs"> the iteration is reduced to this schema (optional) </param> /// <param name="propPath"> the iteration is redurce to this property within the <code>schemaNs</code> </param> /// <param name="options"> advanced iteration options, see <seealso cref="IteratorOptions"/> </param> /// <exception cref="XmpException"> If the node defined by the paramters is not existing. </exception> public XmpIteratorImpl(XmpMetaImpl xmp, string schemaNs, string propPath, IteratorOptions options) { // make sure that options is defined at least with defaults _options = options ?? new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XmpNode startNode; string initialPath = null; bool baseSchema = !String.IsNullOrEmpty(schemaNs); bool baseProperty = !String.IsNullOrEmpty(propPath); if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.Root; } else if (baseSchema && baseProperty) { // Schema and property node provided XmpPath path = XmpPathParser.ExpandXPath(schemaNs, propPath); // base path is the prop path without the property leaf XmpPath basePath = new XmpPath(); for (int i = 0; i < path.Size() - 1; i++) { basePath.Add(path.GetSegment(i)); } startNode = XmpNodeUtils.FindNode(xmp.Root, path, false, null); _baseNs = schemaNs; initialPath = basePath.ToString(); } else if (baseSchema && !baseProperty) { // Only Schema provided startNode = XmpNodeUtils.FindSchemaNode(xmp.Root, schemaNs, false); } else // !baseSchema && baseProperty { // No schema but property provided -> error throw new XmpException("Schema namespace URI is required", XmpError.BADSCHEMA); } // create iterator if (startNode != null) { _nodeIterator = (!_options.JustChildren) ? new NodeIterator(this, startNode, initialPath, 1) : new NodeIteratorChildren(this, startNode, initialPath); } else { // create null iterator _nodeIterator = EmptyList.GetEnumerator(); } }
/// <summary> /// Follow an expanded path expression to find or create a node. /// </summary> /// <param name="xmpTree"> the node to begin the search. </param> /// <param name="xpath"> the complete xpath </param> /// <param name="createNodes"> flag if nodes shall be created /// (when called by <code>setProperty()</code>) </param> /// <param name="leafOptions"> the options for the created leaf nodes (only when /// <code>createNodes == true</code>). </param> /// <returns> Returns the node if found or created or <code>null</code>. </returns> /// <exception cref="XmpException"> An exception is only thrown if an error occurred, /// not if a node was not found. </exception> internal static XmpNode FindNode(XmpNode xmpTree, XmpPath xpath, bool createNodes, PropertyOptions leafOptions) { // check if xpath is set. if (xpath == null || xpath.Size() == 0) { throw new XmpException("Empty XmpPath", XmpError.BADXPATH); } // Root of implicitly created subtree to possible delete it later. // Valid only if leaf is new. XmpNode rootImplicitNode = null; // resolve schema step XmpNode currNode = FindSchemaNode(xmpTree, xpath.GetSegment((int) XmpPath.STEP_SCHEMA).Name, createNodes); if (currNode == null) { return null; } if (currNode.Implicit) { currNode.Implicit = false; // Clear the implicit node bit. rootImplicitNode = currNode; // Save the top most implicit node. } // Now follow the remaining steps of the original XmpPath. try { for (int i = 1; i < xpath.Size(); i++) { currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes); if (currNode == null) { if (createNodes) { // delete implicitly created nodes DeleteNode(rootImplicitNode); } return null; } if (currNode.Implicit) { // clear the implicit node flag currNode.Implicit = false; // if node is an ALIAS (can be only in root step, auto-create array // when the path has been resolved from a not simple alias type if (i == 1 && xpath.GetSegment(i).Alias && xpath.GetSegment(i).AliasForm != 0) { currNode.Options.SetOption(xpath.GetSegment(i).AliasForm, true); } // "CheckImplicitStruct" in C++ else if (i < xpath.Size() - 1 && xpath.GetSegment(i).Kind == XmpPath.STRUCT_FIELD_STEP && !currNode.Options.CompositeProperty) { currNode.Options.Struct = true; } if (rootImplicitNode == null) { rootImplicitNode = currNode; // Save the top most implicit node. } } } } catch (XmpException e) { // if new notes have been created prior to the error, delete them if (rootImplicitNode != null) { DeleteNode(rootImplicitNode); } throw e; } if (rootImplicitNode != null) { // set options only if a node has been successful created currNode.Options.MergeWith(leafOptions); currNode.Options = currNode.Options; } return currNode; }
/** * Split an XmpPath expression apart at the conceptual steps, adding the * root namespace prefix to the first property component. The schema URI is * put in the first (0th) slot in the expanded XmpPath. Check if the top * level component is an alias, but don't resolve it. * <p> * In the most verbose case steps are separated by '/', and each step can be * of these forms: * <dl> * <dt>prefix:name * <dd> A top level property or struct field. * <dt>[index] * <dd> An element of an array. * <dt>[last()] * <dd> The last element of an array. * <dt>[fieldName="value"] * <dd> An element in an array of structs, chosen by a field value. * <dt>[@xml:lang="value"] * <dd> An element in an alt-text array, chosen by the xml:lang qualifier. * <dt>[?qualName="value"] * <dd> An element in an array, chosen by a qualifier value. * <dt>@xml:lang * <dd> An xml:lang qualifier. * <dt>?qualName * <dd> A general qualifier. * </dl> * <p> * The logic is complicated though by shorthand for arrays, the separating * '/' and leading '*' are optional. These are all equivalent: array/*[2] * array/[2] array*[2] array[2] All of these are broken into the 2 steps * "array" and "[2]". * <p> * The value portion in the array selector forms is a string quoted by ''' * or '"'. The value may contain any character including a doubled quoting * character. The value may be empty. * <p> * The syntax isn't checked, but an XML name begins with a letter or '_', * and contains letters, digits, '.', '-', '_', and a bunch of special * non-ASCII Unicode characters. An XML qualified name is a pair of names * separated by a colon. * @param schemaNs * schema namespace * @param path * property name * @return Returns the expandet XmpPath. * @throws XmpException * Thrown if the format is not correct somehow. * */ public static XmpPath ExpandXPath(string schemaNs, string path) { if (schemaNs == null || path == null) { throw new XmpException("Parameter must not be null", XmpError.BADPARAM); } XmpPath expandedXPath = new XmpPath(); PathPosition pos = new PathPosition(); pos.Path = path; // Pull out the first component and do some special processing on it: add the schema // namespace prefix and and see if it is an alias. The start must be a "qualName". ParseRootNode(schemaNs, pos, expandedXPath); // Now continue to process the rest of the XmpPath string. while (pos.StepEnd < path.Length) { pos.StepBegin = pos.StepEnd; SkipPathDelimiter(path, pos); pos.StepEnd = pos.StepBegin; XmpPathSegment segment = path[pos.StepBegin] != '[' ? ParseStructSegment(pos) : ParseIndexSegment(pos); if (segment.Kind == XmpPath.STRUCT_FIELD_STEP) { if (segment.Name[0] == '@') { segment.Name = "?" + segment.Name.Substring(1); if (!"?xml:lang".Equals(segment.Name)) { throw new XmpException("Only xml:lang allowed with '@'", XmpError.BADXPATH); } } if (segment.Name[0] == '?') { pos.NameStart++; segment.Kind = XmpPath.QUALIFIER_STEP; } VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } else if (segment.Kind == XmpPath.FIELD_SELECTOR_STEP) { if (segment.Name[1] == '@') { segment.Name = "[?" + segment.Name.Substring(2); if (!segment.Name.StartsWith("[?xml:lang=")) { throw new XmpException("Only xml:lang allowed with '@'", XmpError.BADXPATH); } } if (segment.Name[1] == '?') { pos.NameStart++; segment.Kind = XmpPath.QUAL_SELECTOR_STEP; VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } } expandedXPath.Add(segment); } return expandedXPath; }
/** * Split an XmpPath expression apart at the conceptual steps, adding the * root namespace prefix to the first property component. The schema URI is * put in the first (0th) slot in the expanded XmpPath. Check if the top * level component is an alias, but don't resolve it. * <p> * In the most verbose case steps are separated by '/', and each step can be * of these forms: * <dl> * <dt>prefix:name * <dd> A top level property or struct field. * <dt>[index] * <dd> An element of an array. * <dt>[last()] * <dd> The last element of an array. * <dt>[fieldName="value"] * <dd> An element in an array of structs, chosen by a field value. * <dt>[@xml:lang="value"] * <dd> An element in an alt-text array, chosen by the xml:lang qualifier. * <dt>[?qualName="value"] * <dd> An element in an array, chosen by a qualifier value. * <dt>@xml:lang * <dd> An xml:lang qualifier. * <dt>?qualName * <dd> A general qualifier. * </dl> * <p> * The logic is complicated though by shorthand for arrays, the separating * '/' and leading '*' are optional. These are all equivalent: array/*[2] * array/[2] array*[2] array[2] All of these are broken into the 2 steps * "array" and "[2]". * <p> * The value portion in the array selector forms is a string quoted by ''' * or '"'. The value may contain any character including a doubled quoting * character. The value may be empty. * <p> * The syntax isn't checked, but an XML name begins with a letter or '_', * and contains letters, digits, '.', '-', '_', and a bunch of special * non-ASCII Unicode characters. An XML qualified name is a pair of names * separated by a colon. * @param schemaNs * schema namespace * @param path * property name * @return Returns the expandet XmpPath. * @throws XmpException * Thrown if the format is not correct somehow. * */ public static XmpPath ExpandXPath(string schemaNs, string path) { if (schemaNs == null || path == null) { throw new XmpException("Parameter must not be null", XmpError.BADPARAM); } XmpPath expandedXPath = new XmpPath(); PathPosition pos = new PathPosition(); pos.Path = path; // Pull out the first component and do some special processing on it: add the schema // namespace prefix and and see if it is an alias. The start must be a "qualName". ParseRootNode(schemaNs, pos, expandedXPath); // Now continue to process the rest of the XmpPath string. while (pos.StepEnd < path.Length) { pos.StepBegin = pos.StepEnd; SkipPathDelimiter(path, pos); pos.StepEnd = pos.StepBegin; XmpPathSegment segment = path[pos.StepBegin] != '[' ? ParseStructSegment(pos) : ParseIndexSegment(pos); if (segment.Kind == XmpPath.STRUCT_FIELD_STEP) { if (segment.Name[0] == '@') { segment.Name = "?" + segment.Name.Substring(1); if (!"?xml:lang".Equals(segment.Name)) { throw new XmpException("Only xml:lang allowed with '@'", XmpError.BADXPATH); } } if (segment.Name[0] == '?') { pos.NameStart++; segment.Kind = XmpPath.QUALIFIER_STEP; } VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } else if (segment.Kind == XmpPath.FIELD_SELECTOR_STEP) { if (segment.Name[1] == '@') { segment.Name = "[?" + segment.Name.Substring(2); if (!segment.Name.StartsWith("[?xml:lang=")) { throw new XmpException("Only xml:lang allowed with '@'", XmpError.BADXPATH); } } if (segment.Name[1] == '?') { pos.NameStart++; segment.Kind = XmpPath.QUAL_SELECTOR_STEP; VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } } expandedXPath.Add(segment); } return(expandedXPath); }