/// <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>Constructor with optional initial values.</summary> /// <remarks>If <c>propName</c> is provided, <c>schemaNS</c> has also be provided.</remarks> /// <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 reduced to this property within the <c>schemaNS</c></param> /// <param name="options">advanced iteration options, see <see cref="IteratorOptions"/></param> /// <exception cref="XmpException">If the node defined by the parameters is not existing.</exception> public XmpIterator(XmpMeta 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 = null; string initialPath = null; var baseSchema = !string.IsNullOrEmpty(schemaNs); var baseProperty = !string.IsNullOrEmpty(propPath); if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.GetRoot(); } else { if (baseSchema && baseProperty) { // Schema and property node provided var path = XmpPathParser.ExpandXPath(schemaNs, propPath); // base path is the prop path without the property leaf var basePath = new XmpPath(); for (var i = 0; i < path.Size() - 1; i++) { basePath.Add(path.GetSegment(i)); } startNode = XmpNodeUtils.FindNode(xmp.GetRoot(), path, false, null); BaseNamespace = schemaNs; initialPath = basePath.ToString(); } else { if (baseSchema && !baseProperty) { // Only Schema provided startNode = XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNs, false); } else { // !baseSchema && baseProperty // No schema but property provided -> error throw new XmpException("Schema namespace URI is required", XmpErrorCode.BadSchema); } } } // create iterator _nodeIterator = startNode != null ? (IIterator)(!Options.IsJustChildren ? new NodeIterator(this, startNode, initialPath, 1) : new NodeIteratorChildren(this, startNode, initialPath)) : Enumerable.Empty <object>().Iterator(); }
/// <summary> /// 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. /// </summary> /// <param name="schemaNs">the root namespace</param> /// <param name="pos">the parsing position helper</param> /// <param name="expandedXPath">the path to contribute to</param> /// <exception cref="XmpException">If the path is not valid.</exception> private 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", XmpErrorCode.BadXPath); } var rootProp = VerifyXPathRoot(schemaNs, pos.Path.Substring(pos.StepBegin, pos.StepEnd - pos.StepBegin)); var aliasInfo = XmpMetaFactory.SchemaRegistry.FindAlias(rootProp); if (aliasInfo == null) { // add schema xpath step expandedXPath.Add(new XmpPathSegment(schemaNs, XmpPathStepType.SchemaNode)); var rootStep = new XmpPathSegment(rootProp, XmpPathStepType.StructFieldStep); expandedXPath.Add(rootStep); return; } // add schema xpath step and base step of alias expandedXPath.Add(new XmpPathSegment(aliasInfo.Namespace, XmpPathStepType.SchemaNode)); expandedXPath.Add(new XmpPathSegment(VerifyXPathRoot(aliasInfo.Namespace, aliasInfo.PropName), XmpPathStepType.StructFieldStep) { IsAlias = true, AliasForm = aliasInfo.AliasForm.GetOptions() }); if (aliasInfo.AliasForm.IsArrayAltText) { expandedXPath.Add(new XmpPathSegment("[?xml:lang='x-default']", XmpPathStepType.QualSelectorStep) { IsAlias = true, AliasForm = aliasInfo.AliasForm.GetOptions() }); } else if (aliasInfo.AliasForm.IsArray) { expandedXPath.Add(new XmpPathSegment("[1]", XmpPathStepType.ArrayIndexStep) { IsAlias = true, AliasForm = aliasInfo.AliasForm.GetOptions() }); } }
/// <summary> /// Split an XMPPath expression apart at the conceptual steps, adding the /// root namespace prefix to the first property component. /// </summary> /// <remarks> /// 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. /// <para /> /// In the most verbose case steps are separated by '/', and each step can be /// of these forms: /// <list> /// <item> /// <term>prefix:name</term> /// <description>A top level property or struct field.</description> /// </item> /// <item> /// <term>[index]</term> /// <description>An element of an array.</description> /// </item> /// <item> /// <term>[last()]</term> /// <description>The last element of an array.</description> /// </item> /// <item> /// <term>[fieldName="value"]</term> /// <description>An element in an array of structs, chosen by a field value.</description> /// </item> /// <item> /// <term>[@xml:lang="value"]</term> /// <description>An element in an alt-text array, chosen by the xml:lang qualifier.</description> /// </item> /// <item> /// <term>[?qualName="value"]</term> /// <description>An element in an array, chosen by a qualifier value.</description> /// </item> /// <item> /// <term>@xml:lang</term> /// <description>An xml:lang qualifier.</description> /// </item> /// <item> /// <term>?qualName</term> /// <description>A general qualifier.</description> /// </item> /// </list> /// <para /> /// 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]". /// <para /> /// 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. /// <para /> /// 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. /// </remarks> /// <param name="schemaNs">schema namespace</param> /// <param name="path">property name</param> /// <returns>Returns the expandet XMPPath.</returns> /// <exception cref="XmpException">Thrown if the format is not correct somehow.</exception> public static XmpPath ExpandXPath(string schemaNs, string path) { if (schemaNs == null || path == null) { throw new XmpException("Parameter must not be null", XmpErrorCode.BadParam); } var expandedXPath = new XmpPath(); var pos = new PathPosition { 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; var segment = path[pos.StepBegin] != '[' ? ParseStructSegment(pos) : ParseIndexSegment(pos); if (segment.Kind == XmpPath.StructFieldStep) { if (segment.Name[0] == '@') { segment.Name = "?" + segment.Name.Substring(1); if (!"?xml:lang".Equals(segment.Name)) { throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } } if (segment.Name[0] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualifierStep; } VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } else if (segment.Kind == XmpPath.FieldSelectorStep) { if (segment.Name[1] == '@') { segment.Name = "[?" + segment.Name.Substring(2); if (!segment.Name.StartsWith("[?xml:lang=")) { throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } } if (segment.Name[1] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualSelectorStep; VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } } expandedXPath.Add(segment); } return(expandedXPath); }
/// <summary> /// Split an XMPPath expression apart at the conceptual steps, adding the /// root namespace prefix to the first property component. /// </summary> /// <remarks> /// 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. /// <para /> /// In the most verbose case steps are separated by '/', and each step can be /// of these forms: /// <list> /// <item> /// <term>prefix:name</term> /// <description>A top level property or struct field.</description> /// </item> /// <item> /// <term>[index]</term> /// <description>An element of an array.</description> /// </item> /// <item> /// <term>[last()]</term> /// <description>The last element of an array.</description> /// </item> /// <item> /// <term>[fieldName="value"]</term> /// <description>An element in an array of structs, chosen by a field value.</description> /// </item> /// <item> /// <term>[@xml:lang="value"]</term> /// <description>An element in an alt-text array, chosen by the xml:lang qualifier.</description> /// </item> /// <item> /// <term>[?qualName="value"]</term> /// <description>An element in an array, chosen by a qualifier value.</description> /// </item> /// <item> /// <term>@xml:lang</term> /// <description>An xml:lang qualifier.</description> /// </item> /// <item> /// <term>?qualName</term> /// <description>A general qualifier.</description> /// </item> /// </list> /// <para /> /// 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]". /// <para /> /// 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. /// <para /> /// 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. /// </remarks> /// <param name="schemaNs">schema namespace</param> /// <param name="path">property name</param> /// <returns>Returns the expandet XMPPath.</returns> /// <exception cref="XmpException">Thrown if the format is not correct somehow.</exception> public static XmpPath ExpandXPath(string schemaNs, string path) { if (schemaNs == null || path == null) throw new XmpException("Parameter must not be null", XmpErrorCode.BadParam); var expandedXPath = new XmpPath(); var pos = new PathPosition { 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; var segment = path[pos.StepBegin] != '[' ? ParseStructSegment(pos) : ParseIndexSegment(pos); if (segment.Kind == XmpPath.StructFieldStep) { if (segment.Name[0] == '@') { segment.Name = "?" + segment.Name.Substring (1); if (!"?xml:lang".Equals(segment.Name)) throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } if (segment.Name[0] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualifierStep; } VerifyQualName(pos.Path.Substring (pos.NameStart, pos.NameEnd - pos.NameStart)); } else if (segment.Kind == XmpPath.FieldSelectorStep) { if (segment.Name[1] == '@') { segment.Name = "[?" + segment.Name.Substring (2); if (!segment.Name.StartsWith("[?xml:lang=")) throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } if (segment.Name[1] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualSelectorStep; VerifyQualName(pos.Path.Substring (pos.NameStart, pos.NameEnd - pos.NameStart)); } } expandedXPath.Add(segment); } return expandedXPath; }
/// <summary> /// 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. /// </summary> /// <param name="schemaNs">the root namespace</param> /// <param name="pos">the parsing position helper</param> /// <param name="expandedXPath">the path to contribute to</param> /// <exception cref="XmpException">If the path is not valid.</exception> private 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", XmpErrorCode.BadXPath); var rootProp = VerifyXPathRoot(schemaNs, pos.Path.Substring (pos.StepBegin, pos.StepEnd - pos.StepBegin)); var aliasInfo = XmpMetaFactory.SchemaRegistry.FindAlias(rootProp); if (aliasInfo == null) { // add schema xpath step expandedXPath.Add(new XmpPathSegment(schemaNs, XmpPath.SchemaNode)); var rootStep = new XmpPathSegment(rootProp, XmpPath.StructFieldStep); expandedXPath.Add(rootStep); } else { // add schema xpath step and base step of alias expandedXPath.Add(new XmpPathSegment(aliasInfo.Namespace, XmpPath.SchemaNode)); var rootStep = new XmpPathSegment(VerifyXPathRoot(aliasInfo.Namespace, aliasInfo.PropName), XmpPath.StructFieldStep); rootStep.IsAlias = true; rootStep.AliasForm = aliasInfo.AliasForm.GetOptions(); expandedXPath.Add(rootStep); if (aliasInfo.AliasForm.IsArrayAltText) { var qualSelectorStep = new XmpPathSegment("[?xml:lang='x-default']", XmpPath.QualSelectorStep); qualSelectorStep.IsAlias = true; qualSelectorStep.AliasForm = aliasInfo.AliasForm.GetOptions(); expandedXPath.Add(qualSelectorStep); } else if (aliasInfo.AliasForm.IsArray) { var indexStep = new XmpPathSegment("[1]", XmpPath.ArrayIndexStep); indexStep.IsAlias = true; indexStep.AliasForm = aliasInfo.AliasForm.GetOptions(); expandedXPath.Add(indexStep); } } }