/// <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> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </summary> /// <param name="aliasNode"> the alias node </param> /// <param name="baseNode"> the base node of the alias </param> /// <param name="outerCall"> marks the outer call of the recursion </param> /// <exception cref="XmpException"> Forwards XMP errors </exception> private static void CompareAliasedSubtrees(XmpNode aliasNode, XmpNode baseNode, bool outerCall) { if (!aliasNode.Value.Equals(baseNode.Value) || aliasNode.ChildrenLength != baseNode.ChildrenLength) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } if (!outerCall && (!aliasNode.Name.Equals(baseNode.Name) || !aliasNode.Options.Equals(baseNode.Options) || aliasNode.QualifierLength != baseNode.QualifierLength)) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } for (IEnumerator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasChild = (XmpNode)an.Current; XmpNode baseChild = (XmpNode)bn.Current; CompareAliasedSubtrees(aliasChild, baseChild, false); } for (IEnumerator an = aliasNode.IterateQualifier(), bn = baseNode.IterateQualifier(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasQual = (XmpNode)an.Current; XmpNode baseQual = (XmpNode)bn.Current; CompareAliasedSubtrees(aliasQual, baseQual, false); } }
/// <summary> /// Recursively handles the "value" for a node. It does not matter if it is a /// top level property, a field of a struct, or an item of an array. The /// indent is that for the property element. An xml:lang qualifier is written /// as an attribute of the property start tag, not by itself forcing the /// qualified property form. The patterns below mostly ignore attribute /// qualifiers like xml:lang. Except for the one struct case, attribute /// qualifiers don't affect the output form. /// /// <blockquote> /// /// <pre> /// <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty> /// /// <ns:UnqualifiedStructProperty> (If no rdf:resource qualifier) /// <rdf:Description> /// ... Fields, same forms as top level properties /// </rdf:Description> /// </ns:UnqualifiedStructProperty> /// /// <ns:ResourceStructProperty rdf:resource="URI" /// ... Fields as attributes /// > /// /// <ns:UnqualifiedArrayProperty> /// <rdf:Bag> or Seq or Alt /// ... Array items as rdf:li elements, same forms as top level properties /// </rdf:Bag> /// </ns:UnqualifiedArrayProperty> /// /// <ns:QualifiedProperty> /// <rdf:Description> /// <rdf:value> ... Property "value" following the unqualified /// forms ... </rdf:value> /// ... Qualifiers looking like named struct fields /// </rdf:Description> /// </ns:QualifiedProperty> /// </pre> /// /// </blockquote> /// </summary> /// <param name="node"> the property node </param> /// <param name="emitAsRdfValue"> property shall be rendered as attribute rather than tag </param> /// <param name="useCanonicalRdf"> use canonical form with inner description tag or /// the compact form with rdf:ParseType="resource" attribute. </param> /// <param name="indent"> the current indent level </param> /// <exception cref="IOException"> Forwards all writer exceptions. </exception> /// <exception cref="XmpException"> If "rdf:resource" and general qualifiers are mixed. </exception> private void SerializeCanonicalRdfProperty(XmpNode node, bool useCanonicalRdf, bool emitAsRdfValue, int indent) { bool emitEndTag = true; bool indentEndTag = true; // Determine the XML element name. Open the start tag with the name and // attribute qualifiers. string elemName = node.Name; if (emitAsRdfValue) { elemName = "rdf:value"; } else if (XmpConst.ARRAY_ITEM_NAME.Equals(elemName)) { elemName = "rdf:li"; } WriteIndent(indent); Write('<'); Write(elemName); bool hasGeneralQualifiers = false; bool hasRdfResourceQual = false; for (IEnumerator it = node.IterateQualifier(); it.MoveNext();) { XmpNode qualifier = (XmpNode) it.Current; if (qualifier != null) { if (!RDF_ATTR_QUALIFIER.Contains(qualifier.Name)) { hasGeneralQualifiers = true; } else { hasRdfResourceQual = "rdf:resource".Equals(qualifier.Name); if (!emitAsRdfValue) { Write(' '); Write(qualifier.Name); Write("=\""); AppendNodeValue(qualifier.Value, true); Write('"'); } } } } // Process the property according to the standard patterns. if (hasGeneralQualifiers && !emitAsRdfValue) { // This node has general, non-attribute, qualifiers. Emit using the // qualified property form. // ! The value is output by a recursive call ON THE SAME NODE with // emitAsRDFValue set. if (hasRdfResourceQual) { throw new XmpException("Can't mix rdf:resource and general qualifiers", XmpError.BADRDF); } // Change serialization to canonical format with inner rdf:Description-tag // depending on option if (useCanonicalRdf) { Write(">"); WriteNewline(); indent++; WriteIndent(indent); Write(RDF_STRUCT_START); Write(">"); } else { Write(" rdf:parseType=\"Resource\">"); } WriteNewline(); SerializeCanonicalRdfProperty(node, useCanonicalRdf, true, indent + 1); for (IEnumerator it = node.IterateQualifier(); it.MoveNext();) { XmpNode qualifier = (XmpNode) it.Current; if (qualifier != null && !RDF_ATTR_QUALIFIER.Contains(qualifier.Name)) { SerializeCanonicalRdfProperty(qualifier, useCanonicalRdf, false, indent + 1); } } if (useCanonicalRdf) { WriteIndent(indent); Write(RDF_STRUCT_END); WriteNewline(); indent--; } } else { // This node has no general qualifiers. Emit using an unqualified form. if (!node.Options.CompositeProperty) { // This is a simple property. if (node.Options.Uri) { Write(" rdf:resource=\""); AppendNodeValue(node.Value, true); Write("\"/>"); WriteNewline(); emitEndTag = false; } else if (node.Value == null || "".Equals(node.Value)) { Write("/>"); WriteNewline(); emitEndTag = false; } else { Write('>'); AppendNodeValue(node.Value, false); indentEndTag = false; } } else if (node.Options.Array) { // This is an array. Write('>'); WriteNewline(); EmitRdfArrayTag(node, true, indent + 1); if (node.Options.ArrayAltText) { XmpNodeUtils.NormalizeLangArray(node); } for (IEnumerator it = node.IterateChildren(); it.MoveNext();) { XmpNode child = (XmpNode) it.Current; SerializeCanonicalRdfProperty(child, useCanonicalRdf, false, indent + 2); } EmitRdfArrayTag(node, false, indent + 1); } else if (!hasRdfResourceQual) { // This is a "normal" struct, use the rdf:parseType="Resource" form. if (!node.HasChildren()) { // Change serialization to canonical format with inner rdf:Description-tag // if option is set if (useCanonicalRdf) { Write(">"); WriteNewline(); WriteIndent(indent + 1); Write(RDF_EMPTY_STRUCT); } else { Write(" rdf:parseType=\"Resource\"/>"); emitEndTag = false; } WriteNewline(); } else { // Change serialization to canonical format with inner rdf:Description-tag // if option is set if (useCanonicalRdf) { Write(">"); WriteNewline(); indent++; WriteIndent(indent); Write(RDF_STRUCT_START); Write(">"); } else { Write(" rdf:parseType=\"Resource\">"); } WriteNewline(); for (IEnumerator it = node.IterateChildren(); it.MoveNext();) { XmpNode child = (XmpNode) it.Current; SerializeCanonicalRdfProperty(child, useCanonicalRdf, false, indent + 1); } if (useCanonicalRdf) { WriteIndent(indent); Write(RDF_STRUCT_END); WriteNewline(); indent--; } } } else { // This is a struct with an rdf:resource attribute, use the // "empty property element" form. for (IEnumerator it = node.IterateChildren(); it.MoveNext();) { XmpNode child = (XmpNode) it.Current; if (child != null) { if (!canBeRDFAttrProp(child)) { throw new XmpException("Can't mix rdf:resource and complex fields", XmpError.BADRDF); } WriteNewline(); WriteIndent(indent + 1); Write(' '); Write(child.Name); Write("=\""); AppendNodeValue(child.Value, true); Write('"'); } } Write("/>"); WriteNewline(); emitEndTag = false; } } // Emit the property element end tag. if (emitEndTag) { if (indentEndTag) { WriteIndent(indent); } Write("</"); Write(elemName); Write('>'); WriteNewline(); } }
/// <summary> /// Writes all used namespaces of the subtree in node to the output. /// The subtree is recursivly traversed. </summary> /// <param name="node"> the root node of the subtree </param> /// <param name="usedPrefixes"> a set containing currently used prefixes </param> /// <param name="indent"> the current indent level </param> /// <exception cref="IOException"> Forwards all writer exceptions. </exception> private void DeclareUsedNamespaces(XmpNode node, ISet usedPrefixes, int indent) { if (node.Options.SchemaNode) { // The schema node name is the URI, the value is the prefix. string prefix = node.Value.Substring(0, node.Value.Length - 1); DeclareNamespace(prefix, node.Name, usedPrefixes, indent); } else if (node.Options.Struct) { for (IEnumerator it = node.IterateChildren(); it.MoveNext();) { XmpNode field = (XmpNode) it.Current; if (field == null) continue; DeclareNamespace(field.Name, null, usedPrefixes, indent); } } for (IEnumerator it = node.IterateChildren(); it.MoveNext();) { XmpNode child = (XmpNode) it.Current; if (child == null) continue; DeclareUsedNamespaces(child, usedPrefixes, indent); } for (IEnumerator it = node.IterateQualifier(); it.MoveNext();) { XmpNode qualifier = (XmpNode) it.Current; if (qualifier == null) continue; DeclareNamespace(qualifier.Name, null, usedPrefixes, indent); DeclareUsedNamespaces(qualifier, usedPrefixes, indent); } }
/// <summary> /// Serializes the general qualifier. </summary> /// <param name="node"> the root node of the subtree </param> /// <param name="indent"> the current indent level </param> /// <exception cref="IOException"> Forwards all writer exceptions. </exception> /// <exception cref="XmpException"> If qualifier and element fields are mixed. </exception> private void SerializeCompactRdfGeneralQualifier(int indent, XmpNode node) { // The node has general qualifiers, ones that can't be // attributes on a property element. // Emit using the qualified property pseudo-struct form. The // value is output by a call // to SerializePrettyRDFProperty with emitAsRDFValue set. Write(" rdf:parseType=\"Resource\">"); WriteNewline(); SerializeCanonicalRdfProperty(node, false, true, indent + 1); for (IEnumerator iq = node.IterateQualifier(); iq.MoveNext();) { XmpNode qualifier = (XmpNode) iq.Current; if (qualifier == null) continue; SerializeCanonicalRdfProperty(qualifier, false, false, indent + 1); } }
/// <summary> /// The outermost call is special. The names almost certainly differ. The /// qualifiers (and hence options) will differ for an alias to the x-default /// item of a langAlt array. /// </summary> /// <param name="aliasNode"> the alias node </param> /// <param name="baseNode"> the base node of the alias </param> /// <param name="outerCall"> marks the outer call of the recursion </param> /// <exception cref="XmpException"> Forwards XMP errors </exception> private static void CompareAliasedSubtrees(XmpNode aliasNode, XmpNode baseNode, bool outerCall) { if (!aliasNode.Value.Equals(baseNode.Value) || aliasNode.ChildrenLength != baseNode.ChildrenLength) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } if (!outerCall && (!aliasNode.Name.Equals(baseNode.Name) || !aliasNode.Options.Equals(baseNode.Options) || aliasNode.QualifierLength != baseNode.QualifierLength)) { throw new XmpException("Mismatch between alias and base nodes", XmpError.BADXMP); } for (IEnumerator an = aliasNode.IterateChildren(), bn = baseNode.IterateChildren(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasChild = (XmpNode) an.Current; XmpNode baseChild = (XmpNode) bn.Current; CompareAliasedSubtrees(aliasChild, baseChild, false); } for (IEnumerator an = aliasNode.IterateQualifier(), bn = baseNode.IterateQualifier(); an.MoveNext() && bn.MoveNext();) { XmpNode aliasQual = (XmpNode) an.Current; XmpNode baseQual = (XmpNode) bn.Current; CompareAliasedSubtrees(aliasQual, baseQual, false); } }