2 // Mono.Xml.XPath.DTMXPathDocumentWriter2
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // (C) 2004 Novell Inc.
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
34 using System.Xml.Schema;
35 using System.Xml.XPath;
37 namespace Mono.Xml.XPath
39 #if OUTSIDE_SYSTEM_XML
44 class DTMXPathDocumentWriter2 : XmlWriter
46 public DTMXPathDocumentWriter2 (XmlNameTable nt, int defaultCapacity)
48 nameTable = nt == null ? new NameTable () : nt;
49 nodeCapacity = defaultCapacity;
50 attributeCapacity = nodeCapacity;
52 idTable = new Hashtable ();
54 nodes = new DTMXPathLinkedNode2 [nodeCapacity];
55 attributes = new DTMXPathAttributeNode2 [attributeCapacity];
56 namespaces = new DTMXPathNamespaceNode2 [nsCapacity];
58 atomicStringPool = new string [20];
59 nonAtomicStringPool = new string [20];
64 XmlNameTable nameTable;
66 int attributeCapacity;
70 DTMXPathLinkedNode2 [] nodes;
73 DTMXPathAttributeNode2 [] attributes;
76 DTMXPathNamespaceNode2 [] namespaces;
79 string [] atomicStringPool;
81 string [] nonAtomicStringPool;
84 // idTable [string value] -> int nodeId
90 int [] parentStack = new int [10];
91 int parentStackIndex = 0;
93 // for attribute processing; should be reset per each element.
101 // They are only used in Writer
107 public DTMXPathDocument2 CreateDocument ()
111 return new DTMXPathDocument2 (nameTable,
123 // string pool index 0 to 3 are fixed.
124 atomicStringPool [0] = nonAtomicStringPool [0] = "";
125 atomicStringPool [1] = nonAtomicStringPool [1] = null;
126 atomicStringPool [2] = nonAtomicStringPool [2] = XmlNamespaces.XML;
127 atomicStringPool [3] = nonAtomicStringPool [3] = XmlNamespaces.XMLNS;
128 atomicIndex = nonAtomicIndex = 4;
130 // index 0 is dummy. No node (including Root) is assigned to this index
131 // So that we can easily compare index != 0 instead of index < 0.
132 // (Difference between jnz or jbe in 80x86.)
133 AddNode (0, 0, 0, XPathNodeType.All, "", false, "", "", "", "", "", 0, 0, 0);
135 AddAttribute (0, "", "", "", "", 0, 0);
136 AddNsNode (0, "", "", 0);
138 AddNsNode (1, "xml", XmlNamespaces.XML, 0);
141 AddNode (0, 0, 0, XPathNodeType.Root, "", false, "", "", "", "", "", 1, 0, 0);
144 this.lastNsInScope = 1;
145 parentStack [0] = nodeIndex;
147 state = WriteState.Content;
150 private int GetParentIndex ()
152 return parentStack [parentStackIndex];
155 private int GetPreviousSiblingIndex ()
157 int parent = parentStack [parentStackIndex];
158 if (parent == nodeIndex)
160 int prevSibling = nodeIndex;
161 while (nodes [prevSibling].Parent != parent)
162 prevSibling = nodes [prevSibling].Parent;
166 private void UpdateTreeForAddition ()
168 int parent = GetParentIndex ();
169 prevSibling = GetPreviousSiblingIndex ();
173 if (prevSibling != 0)
174 nodes [prevSibling].NextSibling = nodeIndex;
175 if (parent == nodeIndex - 1)
176 nodes [parent].FirstChild = nodeIndex;
179 private void CloseStartElement ()
181 if (attrIndexAtStart != attributeIndex)
182 nodes [nodeIndex].FirstAttribute = attrIndexAtStart + 1;
183 if (nsIndexAtStart != nsIndex) {
184 nodes [nodeIndex].FirstNamespace = nsIndex;
185 lastNsInScope = nsIndex;
189 if (parentStack.Length == parentStackIndex) {
190 int [] tmp = new int [parentStackIndex * 2];
191 Array.Copy (parentStack, tmp, parentStackIndex);
194 parentStack [parentStackIndex] = nodeIndex;
196 state = WriteState.Content;
\r
201 private int AtomicIndex (string s)
208 for (; i < atomicIndex; i++)
209 if (Object.ReferenceEquals (s, atomicStringPool [i]))
212 if (atomicIndex == atomicStringPool.Length) {
213 string [] newArr = new string [atomicIndex * 2];
214 Array.Copy (atomicStringPool, newArr, atomicIndex);
215 atomicStringPool = newArr;
217 atomicStringPool [atomicIndex] = s;
218 return atomicIndex++;
221 private int NonAtomicIndex (string s)
229 // Here we don't compare all the entries (sometimes it
230 // goes extremely slow).
231 int max = nonAtomicIndex < 100 ? nonAtomicIndex : 100;
233 if (s == nonAtomicStringPool [i])
236 if (nonAtomicIndex == nonAtomicStringPool.Length) {
237 string [] newArr = new string [nonAtomicIndex * 2];
238 Array.Copy (nonAtomicStringPool, newArr, nonAtomicIndex);
239 nonAtomicStringPool = newArr;
241 nonAtomicStringPool [nonAtomicIndex] = s;
242 return nonAtomicIndex++;
245 private void SetNodeArrayLength (int size)
247 DTMXPathLinkedNode2 [] newArr = new DTMXPathLinkedNode2 [size];
248 Array.Copy (nodes, newArr, System.Math.Min (size, nodes.Length));
252 private void SetAttributeArrayLength (int size)
254 DTMXPathAttributeNode2 [] newArr =
255 new DTMXPathAttributeNode2 [size];
256 Array.Copy (attributes, newArr, System.Math.Min (size, attributes.Length));
260 private void SetNsArrayLength (int size)
262 DTMXPathNamespaceNode2 [] newArr =
263 new DTMXPathNamespaceNode2 [size];
264 Array.Copy (namespaces, newArr, System.Math.Min (size, namespaces.Length));
268 // Here followings are skipped: firstChild, nextSibling,
269 public void AddNode (int parent, int firstAttribute, int previousSibling, XPathNodeType nodeType, string baseUri, bool isEmptyElement, string localName, string ns, string prefix, string value, string xmlLang, int namespaceNode, int lineNumber, int linePosition)
271 if (nodes.Length < nodeIndex + 1) {
273 SetNodeArrayLength (nodeCapacity);
277 nodes [nodeIndex] = new DTMXPathLinkedNode2 ();
279 nodes [nodeIndex].FirstChild = 0; // dummy
280 nodes [nodeIndex].Parent = parent;
281 nodes [nodeIndex].FirstAttribute = firstAttribute;
282 nodes [nodeIndex].PreviousSibling = previousSibling;
283 nodes [nodeIndex].NextSibling = 0; // dummy
284 nodes [nodeIndex].NodeType = nodeType;
285 nodes [nodeIndex].BaseURI = AtomicIndex (baseUri);
286 nodes [nodeIndex].IsEmptyElement = isEmptyElement;
287 nodes [nodeIndex].LocalName = AtomicIndex (localName);
288 nodes [nodeIndex].NamespaceURI = AtomicIndex (ns);
289 nodes [nodeIndex].Prefix = AtomicIndex (prefix);
290 nodes [nodeIndex].Value = NonAtomicIndex (value);
291 nodes [nodeIndex].XmlLang = AtomicIndex (xmlLang);
292 nodes [nodeIndex].FirstNamespace = namespaceNode;
293 nodes [nodeIndex].LineNumber = lineNumber;
294 nodes [nodeIndex].LinePosition = linePosition;
297 // Followings are skipped: nextAttribute,
298 public void AddAttribute (int ownerElement, string localName, string ns, string prefix, string value, int lineNumber, int linePosition)
300 if (attributes.Length < attributeIndex + 1) {
301 attributeCapacity *= 4;
302 SetAttributeArrayLength (attributeCapacity);
306 attributes [attributeIndex] = new DTMXPathAttributeNode2 ();
308 attributes [attributeIndex].OwnerElement = ownerElement;
309 attributes [attributeIndex].LocalName = AtomicIndex (localName);
310 attributes [attributeIndex].NamespaceURI = AtomicIndex (ns);
311 attributes [attributeIndex].Prefix = AtomicIndex (prefix);
312 attributes [attributeIndex].Value = NonAtomicIndex (value);
313 attributes [attributeIndex].LineNumber = lineNumber;
314 attributes [attributeIndex].LinePosition = linePosition;
317 // Followings are skipped: nextNsNode (may be next attribute in the same element, or ancestors' nsNode)
318 public void AddNsNode (int declaredElement, string name, string ns, int nextNs)
320 if (namespaces.Length < nsIndex + 1) {
322 SetNsArrayLength (nsCapacity);
326 namespaces [nsIndex] = new DTMXPathNamespaceNode2 ();
328 namespaces [nsIndex].DeclaredElement = declaredElement;
329 namespaces [nsIndex].Name = AtomicIndex (name);
330 namespaces [nsIndex].Namespace = AtomicIndex (ns);
331 namespaces [nsIndex].NextNamespace = nextNs;
335 #region XmlWriter methods
336 // They are not supported
337 public override string XmlLang { get { return null; } }
338 public override XmlSpace XmlSpace { get { return XmlSpace.None; } }
340 public override WriteState WriteState { get { return state; } }
342 public override void Close ()
345 SetNodeArrayLength (nodeIndex + 1);
346 SetAttributeArrayLength (attributeIndex + 1);
347 SetNsArrayLength (nsIndex + 1);
349 string [] newArr = new string [atomicIndex];
350 Array.Copy (atomicStringPool, newArr, atomicIndex);
351 atomicStringPool = newArr;
353 newArr = new string [nonAtomicIndex];
354 Array.Copy (nonAtomicStringPool, newArr, nonAtomicIndex);
355 nonAtomicStringPool = newArr;
360 public override void Flush ()
365 public override string LookupPrefix (string ns)
369 if (atomicStringPool [namespaces [tmp].Namespace] == ns)
370 return atomicStringPool [namespaces [tmp].Name];
371 tmp = namespaces [tmp].NextNamespace;
376 public override void WriteCData (string data)
\r
378 AddTextNode (data);
\r
381 private void AddTextNode (string data)
\r
384 case WriteState.Element:
\r
385 CloseStartElement ();
\r
387 case WriteState.Content:
\r
390 throw new InvalidOperationException ("Invalid document state for CDATA section: " + state);
\r
393 // When text after text, just add the value, and return.
\r
394 if (nodes [nodeIndex].Parent == parentStack [parentStackIndex]) {
395 switch (nodes [nodeIndex].NodeType) {
\r
396 case XPathNodeType.Text:
\r
397 case XPathNodeType.SignificantWhitespace:
398 string value = nonAtomicStringPool [nodes [nodeIndex].Value] + data;
399 nodes [nodeIndex].Value = NonAtomicIndex (value);
\r
400 if (IsWhitespace (value))
\r
401 nodes [nodeIndex].NodeType = XPathNodeType.SignificantWhitespace;
\r
403 nodes [nodeIndex].NodeType = XPathNodeType.Text;
\r
408 int parent = GetParentIndex ();
\r
409 UpdateTreeForAddition ();
\r
427 private void CheckTopLevelNode ()
\r
430 case WriteState.Element:
\r
431 CloseStartElement ();
\r
433 case WriteState.Content:
\r
434 case WriteState.Prolog:
\r
435 case WriteState.Start:
\r
438 throw new InvalidOperationException ("Invalid document state for CDATA section: " + state);
\r
442 public override void WriteComment (string data)
\r
444 CheckTopLevelNode ();
\r
446 int parent = GetParentIndex ();
\r
447 UpdateTreeForAddition ();
\r
452 XPathNodeType.Comment,
465 public override void WriteProcessingInstruction (string name, string data)
\r
467 CheckTopLevelNode ();
\r
469 int parent = GetParentIndex ();
\r
470 UpdateTreeForAddition ();
\r
475 XPathNodeType.ProcessingInstruction,
488 public override void WriteWhitespace (string data)
\r
490 CheckTopLevelNode ();
\r
492 int parent = GetParentIndex ();
\r
493 UpdateTreeForAddition ();
\r
498 XPathNodeType.Whitespace,
511 public override void WriteStartDocument ()
\r
516 public override void WriteStartDocument (bool standalone)
\r
521 public override void WriteEndDocument ()
\r
526 public override void WriteStartElement (string prefix, string localName, string ns)
\r
530 else if (prefix == null && ns.Length > 0)
531 prefix = LookupPrefix (ns);
533 prefix = String.Empty;
536 case WriteState.Element:
\r
537 CloseStartElement ();
\r
539 case WriteState.Start:
\r
540 case WriteState.Prolog:
\r
541 case WriteState.Content:
\r
544 throw new InvalidOperationException ("Invalid document state for writing element: " + state);
\r
547 int parent = GetParentIndex ();
\r
548 UpdateTreeForAddition ();
\r
550 WriteStartElement (parent, prevSibling, prefix, localName, ns);
551 state = WriteState.Element;
554 private void WriteStartElement (int parent, int previousSibling, string prefix, string localName, string ns)
556 PrepareStartElement (previousSibling);
559 0, // dummy:firstAttribute
561 XPathNodeType.Element,
567 "", // Element has no internal value.
574 private void PrepareStartElement (int previousSibling)
576 hasAttributes = false;
578 attrIndexAtStart = attributeIndex;
579 nsIndexAtStart = nsIndex;
581 while (namespaces [lastNsInScope].DeclaredElement == previousSibling) {
582 lastNsInScope = namespaces [lastNsInScope].NextNamespace;
586 public override void WriteEndElement ()
\r
588 WriteEndElement (false);
\r
591 public override void WriteFullEndElement ()
\r
593 WriteEndElement (true);
\r
596 private void WriteEndElement (bool full)
\r
599 case WriteState.Element:
\r
600 CloseStartElement ();
\r
602 case WriteState.Content:
\r
605 throw new InvalidOperationException ("Invalid state for writing EndElement: " + state);
\r
608 if (nodes [nodeIndex].NodeType == XPathNodeType.Element) {
\r
610 nodes [nodeIndex].IsEmptyElement = true;
\r
614 public override void WriteStartAttribute (string prefix, string localName, string ns)
\r
619 if (state != WriteState.Element)
\r
620 throw new InvalidOperationException ("Invalid document state for attribute: " + state);
\r
622 state = WriteState.Attribute;
\r
623 if (ns == XmlNamespaces.XMLNS)
624 ProcessNamespace ((prefix == null || prefix == String.Empty) ? "" : localName, String.Empty); // dummy: Value should be completed
626 ProcessAttribute (prefix, localName, ns, String.Empty); // dummy: Value should be completed
629 private void ProcessNamespace (string prefix, string ns)
631 int nextTmp = hasLocalNs ? nsIndex : nodes [nodeIndex].FirstNamespace;
635 this.AddNsNode (nodeIndex,
640 openNamespace = true;
643 private void ProcessAttribute (string prefix, string localName, string ns, string value)
645 if (prefix == null && ns.Length > 0)
646 prefix = LookupPrefix (ns);
649 this.AddAttribute (nodeIndex,
652 prefix != null ? prefix : String.Empty,
657 attributes [attributeIndex - 1].NextAttribute = attributeIndex;
659 hasAttributes = true;
662 public override void WriteEndAttribute ()
\r
664 if (state != WriteState.Attribute)
\r
665 throw new InvalidOperationException ();
\r
667 openNamespace = false;
\r
668 state = WriteState.Element;
\r
671 public override void WriteString (string text)
\r
673 if (WriteState == WriteState.Attribute) {
674 if (openNamespace) {
\r
675 string value = atomicStringPool [namespaces [nsIndex].Namespace] + text;
676 namespaces [nsIndex].Namespace = AtomicIndex (value);
678 string value = nonAtomicStringPool [attributes [attributeIndex].Value] + text;
679 attributes [attributeIndex].Value = NonAtomicIndex (value);
683 AddTextNode (text);
\r
686 // Well, they cannot be supported, but actually used to
\r
687 // disable-output-escaping = "true"
\r
688 public override void WriteRaw (string data)
\r
690 WriteString (data);
\r
693 public override void WriteRaw (char [] data, int start, int len)
\r
695 WriteString (new string (data, start, len));
\r
698 public override void WriteName (string name)
\r
700 WriteString (name);
\r
703 public override void WriteNmToken (string name)
\r
705 WriteString (name);
\r
708 public override void WriteBase64 (byte [] buffer, int index, int count)
\r
710 WriteString (Convert.ToBase64String (buffer, index, count));
713 public override void WriteBinHex (byte [] buffer, int index, int count)
\r
715 throw new NotSupportedException ();
\r
718 public override void WriteChars (char [] buffer, int index, int count)
\r
720 WriteString (new string (buffer, index, count));
723 public override void WriteCharEntity (char c)
\r
725 WriteString (c.ToString ());
728 public override void WriteDocType (string name, string pub, string sys, string intSubset)
\r
730 throw new NotSupportedException ();
\r
733 public override void WriteEntityRef (string name)
735 throw new NotSupportedException ();
\r
738 public override void WriteQualifiedName (string localName, string ns)
\r
740 string prefix = (ns == null || ns.Length == 0) ? null : LookupPrefix (ns);
741 if (prefix != null && prefix.Length > 0)
742 WriteString (prefix + ":" + localName);
744 WriteString (localName);
747 public override void WriteSurrogateCharEntity (char high, char low)
\r
749 WriteString (high.ToString ());
750 WriteString (low.ToString ());
753 private bool IsWhitespace (string data)
755 for (int i = 0; i < data.Length; i++) {