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
529 case WriteState.Element:
\r
530 CloseStartElement ();
\r
532 case WriteState.Start:
\r
533 case WriteState.Prolog:
\r
534 case WriteState.Content:
\r
537 throw new InvalidOperationException ("Invalid document state for writing element: " + state);
\r
540 int parent = GetParentIndex ();
\r
541 UpdateTreeForAddition ();
\r
543 WriteStartElement (parent, prevSibling, prefix, localName, ns);
544 state = WriteState.Element;
547 private void WriteStartElement (int parent, int previousSibling, string prefix, string localName, string ns)
549 PrepareStartElement (previousSibling);
552 0, // dummy:firstAttribute
554 XPathNodeType.Element,
560 "", // Element has no internal value.
567 private void PrepareStartElement (int previousSibling)
569 hasAttributes = false;
571 attrIndexAtStart = attributeIndex;
572 nsIndexAtStart = nsIndex;
574 while (namespaces [lastNsInScope].DeclaredElement == previousSibling) {
575 lastNsInScope = namespaces [lastNsInScope].NextNamespace;
579 public override void WriteEndElement ()
\r
581 WriteEndElement (false);
\r
584 public override void WriteFullEndElement ()
\r
586 WriteEndElement (true);
\r
589 private void WriteEndElement (bool full)
\r
592 case WriteState.Element:
\r
593 CloseStartElement ();
\r
595 case WriteState.Content:
\r
598 throw new InvalidOperationException ("Invalid state for writing EndElement: " + state);
\r
601 if (nodes [nodeIndex].NodeType == XPathNodeType.Element) {
\r
603 nodes [nodeIndex].IsEmptyElement = true;
\r
607 public override void WriteStartAttribute (string prefix, string localName, string ns)
\r
609 if (state != WriteState.Element)
\r
610 throw new InvalidOperationException ("Invalid document state for attribute: " + state);
\r
612 state = WriteState.Attribute;
\r
613 if (ns == XmlNamespaces.XMLNS)
614 ProcessNamespace ((prefix == null || prefix == String.Empty) ? "" : localName, String.Empty); // dummy: Value should be completed
616 ProcessAttribute (prefix, localName, ns, String.Empty); // dummy: Value should be completed
619 private void ProcessNamespace (string prefix, string ns)
621 int nextTmp = hasLocalNs ? nsIndex : nodes [nodeIndex].FirstNamespace;
625 this.AddNsNode (nodeIndex,
630 openNamespace = true;
633 private void ProcessAttribute (string prefix, string localName, string ns, string value)
637 this.AddAttribute (nodeIndex,
640 prefix != null ? prefix : String.Empty,
645 attributes [attributeIndex - 1].NextAttribute = attributeIndex;
647 hasAttributes = true;
650 public override void WriteEndAttribute ()
\r
652 if (state != WriteState.Attribute)
\r
653 throw new InvalidOperationException ();
\r
655 openNamespace = false;
\r
656 state = WriteState.Element;
\r
659 public override void WriteString (string text)
\r
661 if (WriteState == WriteState.Attribute) {
662 if (openNamespace) {
\r
663 string value = atomicStringPool [namespaces [nsIndex].Namespace] + text;
664 namespaces [nsIndex].Namespace = AtomicIndex (value);
666 string value = nonAtomicStringPool [attributes [attributeIndex].Value] + text;
667 attributes [attributeIndex].Value = NonAtomicIndex (value);
671 AddTextNode (text);
\r
674 // Well, they cannot be supported, but actually used to
\r
675 // disable-output-escaping = "true"
\r
676 public override void WriteRaw (string data)
\r
678 WriteString (data);
\r
681 public override void WriteRaw (char [] data, int start, int len)
\r
683 WriteString (new string (data, start, len));
\r
686 public override void WriteName (string name)
\r
688 WriteString (name);
\r
691 public override void WriteNmToken (string name)
\r
693 WriteString (name);
\r
696 public override void WriteBase64 (byte [] buffer, int index, int count)
\r
698 throw new NotSupportedException ();
\r
701 public override void WriteBinHex (byte [] buffer, int index, int count)
\r
703 throw new NotSupportedException ();
\r
706 public override void WriteChars (char [] buffer, int index, int count)
\r
708 throw new NotSupportedException ();
\r
711 public override void WriteCharEntity (char c)
\r
713 throw new NotSupportedException ();
\r
716 public override void WriteDocType (string name, string pub, string sys, string intSubset)
\r
718 throw new NotSupportedException ();
\r
721 public override void WriteEntityRef (string name)
723 throw new NotSupportedException ();
\r
726 public override void WriteQualifiedName (string localName, string ns)
\r
728 throw new NotSupportedException ();
\r
731 public override void WriteSurrogateCharEntity (char high, char low)
\r
733 throw new NotSupportedException ();
\r
736 private bool IsWhitespace (string data)
738 for (int i = 0; i < data.Length; i++) {