/// <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="Com.Adobe.Xmp.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", XMPErrorConstants.Badxpath); } string rootProp = VerifyXPathRoot(schemaNS, Sharpen.Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd)); XMPAliasInfo aliasInfo = XMPMetaFactory.GetSchemaRegistry().FindAlias(rootProp); if (aliasInfo == null) { // add schema xpath step expandedXPath.Add(new XMPPathSegment(schemaNS, XMPPath.SchemaNode)); XMPPathSegment rootStep = new XMPPathSegment(rootProp, XMPPath.StructFieldStep); expandedXPath.Add(rootStep); } else { // add schema xpath step and base step of alias expandedXPath.Add(new XMPPathSegment(aliasInfo.GetNamespace(), XMPPath.SchemaNode)); XMPPathSegment rootStep = new XMPPathSegment(VerifyXPathRoot(aliasInfo.GetNamespace(), aliasInfo.GetPropName()), XMPPath.StructFieldStep); rootStep.SetAlias(true); rootStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); expandedXPath.Add(rootStep); if (aliasInfo.GetAliasForm().IsArrayAltText()) { XMPPathSegment qualSelectorStep = new XMPPathSegment("[?xml:lang='x-default']", XMPPath.QualSelectorStep); qualSelectorStep.SetAlias(true); qualSelectorStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); expandedXPath.Add(qualSelectorStep); } else { if (aliasInfo.GetAliasForm().IsArray()) { XMPPathSegment indexStep = new XMPPathSegment("[1]", XMPPath.ArrayIndexStep); indexStep.SetAlias(true); indexStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions()); expandedXPath.Add(indexStep); } } } }
// empty /// <summary> /// Split an XMPPath expression apart at the conceptual steps, adding the /// root namespace prefix to the first property component. /// </summary> /// <remarks> /// 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. /// </remarks> /// <param name="schemaNS">schema namespace</param> /// <param name="path">property name</param> /// <returns>Returns the expandet XMPPath.</returns> /// <exception cref="Com.Adobe.Xmp.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", XMPErrorConstants.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; if (path[pos.stepBegin] != '[') { // A struct field or qualifier. segment = ParseStructSegment(pos); } else { // One of the array forms. segment = ParseIndexSegment(pos); } if (segment.GetKind() == XMPPath.StructFieldStep) { if (segment.GetName()[0] == '@') { segment.SetName("?" + Sharpen.Runtime.Substring(segment.GetName(), 1)); if (!"?xml:lang".Equals(segment.GetName())) { throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); } } if (segment.GetName()[0] == '?') { pos.nameStart++; segment.SetKind(XMPPath.QualifierStep); } VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); } else { if (segment.GetKind() == XMPPath.FieldSelectorStep) { if (segment.GetName()[1] == '@') { segment.SetName("[?" + Sharpen.Runtime.Substring(segment.GetName(), 2)); if (!segment.GetName().StartsWith("[?xml:lang=")) { throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); } } if (segment.GetName()[1] == '?') { pos.nameStart++; segment.SetKind(XMPPath.QualSelectorStep); VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); } } } expandedXPath.Add(segment); } return(expandedXPath); }
// empty /// <summary> /// Split an XMPPath expression apart at the conceptual steps, adding the /// root namespace prefix to the first property component. /// </summary> /// <remarks> /// 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. /// </remarks> /// <param name="schemaNS">schema namespace</param> /// <param name="path">property name</param> /// <returns>Returns the expandet XMPPath.</returns> /// <exception cref="Com.Adobe.Xmp.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", XMPErrorConstants.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; if (path[pos.stepBegin] != '[') { // A struct field or qualifier. segment = ParseStructSegment(pos); } else { // One of the array forms. segment = ParseIndexSegment(pos); } if (segment.GetKind() == XMPPath.StructFieldStep) { if (segment.GetName()[0] == '@') { segment.SetName("?" + Sharpen.Runtime.Substring(segment.GetName(), 1)); if (!"?xml:lang".Equals(segment.GetName())) { throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); } } if (segment.GetName()[0] == '?') { pos.nameStart++; segment.SetKind(XMPPath.QualifierStep); } VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); } else { if (segment.GetKind() == XMPPath.FieldSelectorStep) { if (segment.GetName()[1] == '@') { segment.SetName("[?" + Sharpen.Runtime.Substring(segment.GetName(), 2)); if (!segment.GetName().StartsWith("[?xml:lang=")) { throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath); } } if (segment.GetName()[1] == '?') { pos.nameStart++; segment.SetKind(XMPPath.QualSelectorStep); VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd)); } } } expandedXPath.Add(segment); } return expandedXPath; }
/// <summary>Constructor with optionsl initial values.</summary> /// <remarks> /// Constructor with optionsl initial values. If <code>propName</code> is provided, /// <code>schemaNS</code> 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 redurce to this property within the <code>schemaNS</code></param> /// <param name="options"> /// advanced iteration options, see /// <see cref="Com.Adobe.Xmp.Options.IteratorOptions"/> /// </param> /// <exception cref="Com.Adobe.Xmp.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 this.options = options != null ? options : new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XMPNode startNode = null; string initialPath = null; bool baseSchema = schemaNS != null && schemaNS.Length > 0; bool baseProperty = propPath != null && propPath.Length > 0; if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.GetRoot(); } 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.GetRoot(), path, false, null); baseNS = 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", XMPErrorConstants.Badschema); } } } // create iterator if (startNode != null) { if (!this.options.IsJustChildren()) { nodeIterator = new XMPIteratorImpl.NodeIterator(this, startNode, initialPath, 1); } else { nodeIterator = new XMPIteratorImpl.NodeIteratorChildren(this, startNode, initialPath); } } else { // create null iterator nodeIterator = Sharpen.Collections.EmptyList().Iterator(); } }