Εργασία με το Document Node Tree
Εργασία με το Δέντρο Κόμβων του Εγγράφου
Αυτός ο οδηγός δείχνει πώς να περιηγηθείτε και να χειριστείτε τη δομή ενός εγγράφου απευθείας μέσω του δέντρου κόμβων — το μοντέλο αντικειμένων στο οποίο Document, Section, Paragraph, Run, Table και κάθε άλλο στοιχείο εγγράφου χτίζονται. Κάθε στοιχείο σε ένα έγγραφο προέρχεται από το Node, και κάθε στοιχείο που μπορεί να περιέχει παιδιά προέρχεται από το CompositeNode, οπότε η κατανόηση αυτών των δύο βασικών κλάσεων εξηγεί πώς να εργάζεστε με περιεχόμενο που δεν καλύπτεται ήδη από κάποιο αφιερωμένο βοηθητικό API.
Οι Βασικές Κλάσεις Node και CompositeNode
Node είναι η αφηρημένη βασική κλάση για κάθε αντικείμενο σε ένα δέντρο εγγράφου. Εκθέτει το NodeType (μια τιμή enum που προσδιορίζει το είδος του κόμβου), το ParentNode (το περιβάλλον CompositeNode), το Document (το ιδιοκτήτη έγγραφο), και τα PreviousSibling/NextSibling για μετακίνηση μεταξύ κόμβων στο ίδιο επίπεδο. Το Node.IsComposite αναφέρει αν ένας συγκεκριμένος κόμβος μπορεί να περιέχει παιδιά. Το CompositeNode επεκτείνει το Node για στοιχεία δοχείων — τα FirstChild, LastChild και Count περιγράφουν τα άμεσα παιδιά του, και το HasChildNodes αναφέρει αν υπάρχουν. Το CompositeNode.GetChildNodes(nodeType, isDeep) ανακτά όλα τα παιδιά ενός δεδομένου NodeType, προαιρετικά αναζητώντας ολόκληρο το υποδέντρο (isDeep) αντί μόνο των άμεσων παιδιών, και το CompositeNode.GetChild(nodeType, index, isDeep) ανακτά ένα μόνο με βάση το δείκτη.
Η NodeType Enum
NodeType απαριθμεί κάθε είδος κόμβου που ορίζει το μοντέλο εγγράφου — δομικούς τύπους όπως Document, Section, Body, Paragraph, Table, Row και Cell; τύπους ενσωματωμένου περιεχομένου όπως Run, Shape και Comment; και τύπους μηχανισμού πεδίων όπως FieldStart, FieldSeparator και FieldEnd, που σημειώνουν τα όρια και το κείμενο εναλλαγής ενός πεδίου Word μέσα στη ροή εκτέλεσης. Η μεταβίβαση μιας τιμής NodeType στο GetChildNodes ή GetChild περιορίζει την αναζήτηση σε κόμβους ακριβώς αυτού του τύπου, ή περάστε NodeType.Any για αντιστοίχιση σε κάθε τύπο κόμβου.
Περιήγηση και Τροποποίηση του Δέντρου
CompositeNode.GetEnumerator() σας επιτρέπει να επαναλαμβάνετε τα άμεσα παιδιά ενός κόμβου, και το CompositeNode υλοποιεί επίσης το SelectNodes(xpath) και το SelectSingleNode(xpath) για ερωτήματα βάσει XPath σε όλο το υποδέντρο. Για να τροποποιήσετε το δέντρο, τα AppendChild(newChild) και PrependChild(newChild) προσθέτουν έναν κόμβο στην αρχή ή στο τέλος των παιδιών, τα InsertBefore(newChild, refChild) και InsertAfter(newChild, refChild) εισάγουν σχετικά με έναν υπάρχοντα παιδί, και τα RemoveChild(oldChild) ή RemoveAllChildren() αφαιρούν κόμβους. Το CompositeNode.IndexOf(child) επιστρέφει τη θέση ενός παιδιού. Στο ίδιο το Node, το Clone(isCloneChildren) δημιουργεί ένα αντίγραφο — προαιρετικά βαθύ — και το Remove() αποσυνδέει τον κόμβο από τον γονέα του. Τα Node.NextPreOrder(rootNode) και PreviousPreOrder(rootNode) περνούν μέσα από το δέντρο με τη σειρά του εγγράφου, περιορισμένα από έναν δοσμένο ριζικό κόμβο, που είναι ένας κοινός τρόπος για σάρωση ολόκληρου υποδέντρου κόμβο-κατά-κόμβο χωρίς επαναληπτικό κώδικα.
Συλλογές Κόμβων και Ειδοποιήσεις Αλλαγών
NodeCollection είναι μια συγκεκριμένη, μεταβλητή συλλογή κόμβων που υποστηρίζει τα Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) και ToArray(), μαζί με μια ιδιότητα Count. Τα NodeList και NodeEnumerator υποστηρίζουν επανάληψη τύπου ανάγνωσης πάνω σε αποτελέσματα ερωτημάτων, όπως οι κόμβοι που επιστρέφει το SelectNodes. Για την παρακολούθηση δομικών αλλαγών καθώς συμβαίνουν, υλοποιήστε το INodeChangingCallback — οι μέθοδοι του NodeInserting(args), NodeInserted(args), NodeRemoving(args) και NodeRemoved(args) λαμβάνουν καθεμία ένα NodeChangingArgs που περιγράφει το επηρεασμένο Node, το OldParent/NewParent του, και το NodeChangingAction που συνέβη.
Επίσκεψη Κόμβων με DocumentVisitor
Για την επεξεργασία κάθε τύπου κόμβου με δομημένο τρόπο χωρίς χειροκίνητη διέλευση του δέντρου, κληρονομήστε το DocumentVisitor και υπερκαλύψτε τις μεθόδους Visit*Start/Visit*End για τους τύπους κόμβων που σας ενδιαφέρουν — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun κ.λπ. —, στη συνέχεια καλέστε το Node.Accept(visitor) στη ρίζα του υποδέντρου για να το εκτελέσετε. Αυτό είναι το τυπικό πρότυπο για εξαγωγή, μετασχηματισμό ή επιθεώρηση ενός εγγράφου χωρίς να γράφετε προσαρμοσμένη επαναληπτική περιήγηση για κάθε περίπτωση χρήσης.
Συμβουλές και Καλές Πρακτικές
- Χρησιμοποιήστε το
NodeType.Anyμε τοGetChildNodes(NodeType.Any, true)όταν χρειάζεστε κάθε απόγονο ανεξάρτητα από τον τύπο, αντί να καλέτε τοGetChildNodesμία φορά ανά τύπο. - Προτιμήστε το
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)για μια επίπεδη, μη επαναληπτική σάρωση ενός υποδέντρου αντί να γράφετε χειροκίνητη επαναληπτική κατάβαση. - Υλοποιήστε το
DocumentVisitorαντί για κώδικα διαδρομής δέντρου ad hoc όταν η ίδια επεξεργασία πρέπει να εκτελείται σε πολλούς διαφορετικούς τύπους κόμβων. - Ελέγξτε το
Node.IsCompositeπριν κάνετε cast στοCompositeNodeόταν εργάζεστε με έναν κόμβο άγνωστου τύπου. - Καταχωρίστε ένα
INodeChangingCallbackόταν χρειάζεται να αντιδράσετε σε εισαγωγές ή αφαιρέσεις που γίνονται από άλλο κώδικα (συμπεριλαμβανομένων των ενσωματωμένων λειτουργιών), όχι μόνο στις αλλαγές που κάνει άμεσα ο δικός σας κώδικας.
Συνηθισμένα προβλήματα
| Πρόβλημα | Αιτία | Διόρθωση |
|---|---|---|
Η μετατροπή ενός Node σε CompositeNode προκαλεί σφάλμα μη έγκυρης μετατροπής | Ο κόμβος είναι τύπου φύλλου (π.χ. Run ή FieldStart) που δεν μπορεί να περιέχει παιδιά | Ελέγξτε πρώτα το Node.IsComposite, ή χρησιμοποιήστε το NodeType για να επιβεβαιώσετε ότι ο κόμβος είναι τύπου κοντέινερ πριν από τη μετατροπή |
GetChildNodes επιστρέφει μόνο άμεσα παιδιά | isDeep άφησε false (ή παραλείφθηκε όπου η υπερφόρτωση προεπιλέγεται ως ρηχή) | Περνάτε το isDeep: true για να αναζητήσετε ολόκληρο το υποδέντρο |
| Η τροποποίηση μιας συλλογής ενώ γίνεται επανάληψη πάνω της προκαλεί σφάλμα | Προστέθηκαν ή αφαιρέθηκαν κόμβοι μέσα σε ένα foreach πάνω από NodeCollection ή το αποτέλεσμα του GetChildNodes | Δημιουργήστε πρώτα τους κόμβους προς τροποποίηση με το ToArray(), έπειτα επαναλάβετε πάνω στον πίνακα ενώ μεταβάλλετε το δέντρο |
Μια υποκατηγορία του DocumentVisitor δεν καλείται για ορισμένο περιεχόμενο | Ο επισκέπτης κλήθηκε μέσω Accept() σε έναν κόμβο που δεν περιέχει το στοχευόμενο περιεχόμενο, ή η αντίστοιχη μέθοδος Visit* δεν παρακάμφθηκε | Καλέστε το Accept() σε έναν πρόγονο που περιέχει το περιεχόμενο και παρακάμψτε κάθε ζεύγος Visit*Start/Visit*End σχετικό με τους τύπους κόμβων που επεξεργάζονται |
FAQ
Ποια είναι η διαφορά μεταξύ Node και CompositeNode;
Node είναι η βασική κλάση από την οποία κληρονομούν όλα τα στοιχεία του εγγράφου. Το CompositeNode επεκτείνει το Node για στοιχεία που μπορούν να περιέχουν υπο-στοιχεία (όπως τα Body, Paragraph και Table); τα φύλλα (όπως το Run) κληρονομούν απευθείας από το Node και δεν μπορούν να έχουν υπο-στοιχεία.
Πώς μπορώ να βρω όλους τους κόμβους ενός συγκεκριμένου τύπου σε ένα έγγραφο;
Καλέστε το CompositeNode.GetChildNodes(nodeType, isDeep) στον κόμβο που αποτελεί τη ρίζα της αναζήτησής σας (συχνά ο ίδιος ο Document), περνώντας το NodeType που θέλετε και το isDeep: true για να αναζητήσετε ολόκληρο το υποδέντρο, όχι μόνο τα άμεσα παιδιά.
Πώς μπορώ να αφαιρέσω με ασφάλεια κόμβους ενώ κάνω επανάληψη;
Συλλέξτε πρώτα τους κόμβους που θα αφαιρεθούν σε έναν πίνακα — για παράδειγμα με το NodeCollection.ToArray() —, στη συνέχεια επαναλάβετε πάνω σε αυτόν τον πίνακα και καλέστε το Remove() σε κάθε κόμβο, αντί να τροποποιείτε τη ζωντανή συλλογή ενώ ένα foreach την επαναλαμβάνει.
Τι είναι οι κόμβοι FieldStart, FieldSeparator και FieldEnd;
Είναι τιμές NodeType που σηματοδοτούν τη μηχανή ενός πεδίου Word ενσωματωμένου στο ρεύμα εκτέλεσης: τα FieldStart και FieldEnd περιορίζουν το πεδίο, και το FieldSeparator διαχωρίζει τον κώδικα του πεδίου από το τρέχον εμφανιζόμενο αποτέλεσμα.
Πώς μπορώ να επεξεργαστώ κάθε παράγραφο, πίνακα και run σε ένα έγγραφο;
Δημιουργήστε υποκλάση του DocumentVisitor, παρακάμπτετε τα VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd και VisitRun για τους τύπους κόμβων που χρειάζεστε, στη συνέχεια καλέστε το Accept(visitor) στο Document (ή σε οποιαδήποτε ρίζα υποδέντρου) για να το εκτελέσετε.
API Reference Περίληψη
| Κλάση/Μέθοδος | Περιγραφή |
|---|---|
Node | Αφηρημένη βασική κλάση για κάθε κόμβο σε ένα δέντρο εγγράφου |
CompositeNode | Βασική κλάση για κόμβους που μπορούν να περιέχουν παιδιά |
CompositeNode.GetChildNodes(nodeType, isDeep) | Ανακτήστε όλα τα παιδιά ενός συγκεκριμένου τύπου, προαιρετικά σε ολόκληρο το υποδέντρο |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | Προσθέστε έναν κόμβο σε ένα δοχείο στο τέλος, ή πριν/μετά από ένα υπάρχον παιδί |
NodeType | Απαρίθμηση που προσδιορίζει τον τύπο ενός κόμβου (Paragraph, Table, Run, FieldStart, κ.λπ.) |
NodeCollection | Συγκεκριμένη, μεταβλητή συλλογή αντικειμένων Node |
Node.Clone(isCloneChildren) | Αντιγράψτε έναν κόμβο, προαιρετικά συμπεριλαμβάνοντας τα παιδιά του |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | Περιηγηθείτε σε ένα υποδέντρο με τη σειρά του εγγράφου |
INodeChangingCallback | Διεπαφή callback που ειδοποιείται όταν εισάγονται ή αφαιρούνται κόμβοι |
DocumentVisitor | Βασική κλάση για δομημένη, με βάση τον τύπο, διάσχιση μέσω Node.Accept(visitor) |