1 //========================================================================
2 //
3 // XFAScanner.cc
4 //
5 // Copyright 2020 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 #include <aconf.h>
10 
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14 
15 #include "GString.h"
16 #include "GHash.h"
17 #include "Object.h"
18 #include "Error.h"
19 #include "Zoox.h"
20 #include "XFAScanner.h"
21 
22 //------------------------------------------------------------------------
23 
24 // fields have two names:
25 //
26 // name:
27 //   - nodes with bind=global set the index to 0 ("foo[0]") regardless
28 //     of the number of nodes with the same name
29 //   - nodes with bind=none are dropped from the name
30 //   - <area> nodes are dropped from the name
31 //   - used for field value lookup in <xfa:datasets>
32 //
33 // fullName:
34 //   - all named nodes are treated the same, regardless of bind=global
35 //     or bind=none
36 //   - <area> nodes are included in the name, but don't reset the
37 //     numbering (i.e., <area> nodes are "transparent" with respect to
38 //     node numbering)
39 //   - used for field value lookup in <form>
40 //   - used for matching with AcroForm names
41 //
42 // Both names use indexes on all nodes, even if there's only one node
43 // with the name -- this isn't correct for XFA naming, but it matches
44 // the AcroForm behavior.
45 
46 //------------------------------------------------------------------------
47 
XFAFieldLayoutInfo(XFAFieldLayoutHAlign hAlignA,XFAFieldLayoutVAlign vAlignA)48 XFAFieldLayoutInfo::XFAFieldLayoutInfo(XFAFieldLayoutHAlign hAlignA,
49 				       XFAFieldLayoutVAlign vAlignA) {
50   hAlign = hAlignA;
51   vAlign = vAlignA;
52 }
53 
54 //------------------------------------------------------------------------
55 
XFAFieldPictureInfo(XFAFieldPictureSubtype subtypeA,GString * formatA)56 XFAFieldPictureInfo::XFAFieldPictureInfo(XFAFieldPictureSubtype subtypeA,
57 					 GString *formatA) {
58   subtype = subtypeA;
59   format = formatA;
60 }
61 
~XFAFieldPictureInfo()62 XFAFieldPictureInfo::~XFAFieldPictureInfo() {
63   delete format;
64 }
65 
66 //------------------------------------------------------------------------
67 
XFAFieldBarcodeInfo(GString * barcodeTypeA,double wideNarrowRatioA,double moduleWidthA,double moduleHeightA,int dataLengthA,int errorCorrectionLevelA,GString * textLocationA)68 XFAFieldBarcodeInfo::XFAFieldBarcodeInfo(GString *barcodeTypeA,
69 					 double wideNarrowRatioA,
70 					 double moduleWidthA,
71 					 double moduleHeightA,
72 					 int dataLengthA,
73 					 int errorCorrectionLevelA,
74 					 GString *textLocationA) {
75   barcodeType = barcodeTypeA;
76   wideNarrowRatio = wideNarrowRatioA;
77   moduleWidth = moduleWidthA;
78   moduleHeight = moduleHeightA;
79   dataLength = dataLengthA;
80   errorCorrectionLevel = errorCorrectionLevelA;
81   textLocation = textLocationA;
82 }
83 
~XFAFieldBarcodeInfo()84 XFAFieldBarcodeInfo::~XFAFieldBarcodeInfo() {
85   delete barcodeType;
86   delete textLocation;
87 }
88 
89 //------------------------------------------------------------------------
90 
XFAField(GString * nameA,GString * fullNameA,GString * valueA,XFAFieldLayoutInfo * layoutInfoA,XFAFieldPictureInfo * pictureInfoA,XFAFieldBarcodeInfo * barcodeInfoA)91 XFAField::XFAField(GString *nameA, GString *fullNameA, GString *valueA,
92 		   XFAFieldLayoutInfo *layoutInfoA,
93 		   XFAFieldPictureInfo *pictureInfoA,
94 		   XFAFieldBarcodeInfo *barcodeInfoA)
95   : name(nameA)
96   , fullName(fullNameA)
97   , value(valueA)
98   , layoutInfo(layoutInfoA)
99   , pictureInfo(pictureInfoA)
100   , barcodeInfo(barcodeInfoA)
101 {
102 }
103 
~XFAField()104 XFAField::~XFAField() {
105   delete name;
106   delete fullName;
107   delete value;
108   delete layoutInfo;
109   delete pictureInfo;
110   delete barcodeInfo;
111 }
112 
113 //------------------------------------------------------------------------
114 
load(Object * xfaObj)115 XFAScanner *XFAScanner::load(Object *xfaObj) {
116   GString *xfaData = readXFAStreams(xfaObj);
117   if (!xfaData) {
118     return NULL;
119   }
120   ZxDoc *xml = ZxDoc::loadMem(xfaData->getCString(), xfaData->getLength());
121   delete xfaData;
122   if (!xml) {
123     error(errSyntaxError, -1, "Invalid XML in XFA form");
124     return NULL;
125   }
126 
127   XFAScanner *scanner = new XFAScanner();
128 
129   if (xml->getRoot()) {
130     GHash *formValues = scanner->scanFormValues(xml->getRoot());
131     ZxElement *dataElem = NULL;
132     ZxElement *datasets =
133         xml->getRoot()->findFirstChildElement("xfa:datasets");
134     if (datasets) {
135       dataElem = datasets->findFirstChildElement("xfa:data");
136     }
137     ZxElement *tmpl = xml->getRoot()->findFirstChildElement("template");
138     if (tmpl) {
139       scanner->scanNode(tmpl, NULL, NULL, NULL, NULL, NULL,
140 			dataElem, formValues);
141     }
142     deleteGHash(formValues, GString);
143   }
144 
145   delete xml;
146 
147   return scanner;
148 }
149 
XFAScanner()150 XFAScanner::XFAScanner() {
151   fields = new GHash();
152 }
153 
~XFAScanner()154 XFAScanner::~XFAScanner() {
155   deleteGHash(fields, XFAField);
156 }
157 
findField(GString * acroFormFieldName)158 XFAField *XFAScanner::findField(GString *acroFormFieldName) {
159   return (XFAField *)fields->lookup(acroFormFieldName);
160 }
161 
readXFAStreams(Object * xfaObj)162 GString *XFAScanner::readXFAStreams(Object *xfaObj) {
163   GString *data = new GString();
164   char buf[4096];
165   int n;
166   if (xfaObj->isStream()) {
167     xfaObj->streamReset();
168     while ((n = xfaObj->getStream()->getBlock(buf, sizeof(buf))) > 0) {
169       data->append(buf, n);
170     }
171   } else if (xfaObj->isArray()) {
172     for (int i = 1; i < xfaObj->arrayGetLength(); i += 2) {
173       Object obj;
174       if (!xfaObj->arrayGet(i, &obj)->isStream()) {
175 	error(errSyntaxError, -1, "XFA array element is wrong type");
176 	obj.free();
177 	delete data;
178 	return NULL;
179       }
180       obj.streamReset();
181       while ((n = obj.getStream()->getBlock(buf, sizeof(buf))) > 0) {
182 	data->append(buf, n);
183       }
184       obj.free();
185     }
186   } else {
187     error(errSyntaxError, -1, "XFA object is wrong type");
188     return NULL;
189   }
190   return data;
191 }
192 
scanFormValues(ZxElement * xmlRoot)193 GHash *XFAScanner::scanFormValues(ZxElement *xmlRoot) {
194   GHash *formValues = new GHash(gTrue);
195   ZxElement *formElem = xmlRoot->findFirstChildElement("form");
196   if (formElem) {
197     scanFormNode(formElem, NULL, formValues);
198   }
199   return formValues;
200 }
201 
scanFormNode(ZxElement * elem,GString * fullName,GHash * formValues)202 void XFAScanner::scanFormNode(ZxElement *elem, GString *fullName,
203 			      GHash *formValues) {
204   GHash *fullNameIdx = new GHash();
205   for (ZxNode *node = elem->getFirstChild();
206        node;
207        node = node->getNextChild()) {
208     if (node->isElement("value")) {
209       if (fullName) {
210 	ZxNode *child1Node = ((ZxElement *)node)->getFirstChild();
211 	if (child1Node && child1Node->isElement()) {
212 	  ZxNode *child2Node = ((ZxElement *)child1Node)->getFirstChild();
213 	  if (child2Node && child2Node->isCharData()) {
214 	    formValues->add(fullName->copy(),
215 			    ((ZxCharData *)child2Node)->getData()->copy());
216 	  }
217 	}
218       }
219     } else if (node->isElement()) {
220       ZxAttr *nameAttr = ((ZxElement *)node)->findAttr("name");
221       if (nameAttr && (node->isElement("subform") ||
222 		       node->isElement("field"))) {
223 	GString *nodeName = nameAttr->getValue();
224 	GString *childFullName;
225 	if (fullName) {
226 	  childFullName = GString::format("{0:t}.{1:t}", fullName, nodeName);
227 	} else {
228 	  childFullName = nodeName->copy();
229 	}
230 	int idx = fullNameIdx->lookupInt(nodeName);
231 	childFullName->appendf("[{0:d}]", idx);
232 	fullNameIdx->replace(nodeName, idx + 1);
233 	scanFormNode((ZxElement *)node, childFullName, formValues);
234 	delete childFullName;
235       } else if (node->isElement("subform")) {
236 	scanFormNode((ZxElement *)node, fullName, formValues);
237       }
238     }
239   }
240   delete fullNameIdx;
241 }
242 
scanNode(ZxElement * elem,GString * parentName,GString * parentFullName,GHash * nameIdx,GHash * fullNameIdx,GString * exclGroupName,ZxElement * dataElem,GHash * formValues)243 void XFAScanner::scanNode(ZxElement *elem,
244 			  GString *parentName, GString *parentFullName,
245 			  GHash *nameIdx, GHash *fullNameIdx,
246 			  GString *exclGroupName, ZxElement *dataElem,
247 			  GHash *formValues) {
248   GString *nodeName = getNodeName(elem);
249   GHash *childNameIdx;
250   if (!nameIdx || nodeName) {
251     childNameIdx = new GHash();
252   } else {
253     childNameIdx = nameIdx;
254   }
255   GString *nodeFullName = getNodeFullName(elem);
256   GHash *childFullNameIdx;
257   if (!fullNameIdx || (nodeFullName && !elem->isElement("area"))) {
258     childFullNameIdx = new GHash();
259   } else {
260     childFullNameIdx = fullNameIdx;
261   }
262 
263   GString *childName;
264   if (nodeName) {
265     if (parentName) {
266       childName = GString::format("{0:t}.{1:t}", parentName, nodeName);
267     } else {
268       childName = nodeName->copy();
269     }
270     int idx = nameIdx->lookupInt(nodeName);
271     nameIdx->replace(nodeName, idx + 1);
272     if (nodeIsBindGlobal(elem)) {
273       childName->appendf("[0]");
274     } else {
275       childName->appendf("[{0:d}]", idx);
276     }
277   } else {
278     childName = parentName;
279   }
280   GString *childFullName;
281   if (nodeFullName) {
282     if (parentFullName) {
283       childFullName = GString::format("{0:t}.{1:t}",
284 				      parentFullName, nodeFullName);
285     } else {
286       childFullName = nodeFullName->copy();
287     }
288     int idx = fullNameIdx->lookupInt(nodeFullName);
289     fullNameIdx->replace(nodeFullName, idx + 1);
290     childFullName->appendf("[{0:d}]", idx);
291   } else {
292     childFullName = parentFullName;
293   }
294 
295   if (elem->isElement("field")) {
296     scanField(elem, childName, childFullName, exclGroupName,
297 	      dataElem, formValues);
298   } else {
299     GString *childExclGroupName;
300     if (elem->isElement("exclGroup")) {
301       childExclGroupName = childName;
302     } else {
303       childExclGroupName = NULL;
304     }
305     for (ZxNode *child = elem->getFirstChild();
306 	 child;
307 	 child = child->getNextChild()) {
308       if (child->isElement()) {
309 	scanNode((ZxElement *)child, childName, childFullName,
310 		 childNameIdx, childFullNameIdx, childExclGroupName,
311 		 dataElem, formValues);
312       }
313     }
314   }
315 
316   if (childName != parentName) {
317     delete childName;
318   }
319   if (childFullName != parentFullName) {
320     delete childFullName;
321   }
322   if (childNameIdx != nameIdx) {
323     delete childNameIdx;
324   }
325   if (childFullNameIdx != fullNameIdx) {
326     delete childFullNameIdx;
327   }
328 }
329 
scanField(ZxElement * elem,GString * name,GString * fullName,GString * exclGroupName,ZxElement * dataElem,GHash * formValues)330 void XFAScanner::scanField(ZxElement *elem, GString *name, GString *fullName,
331 			   GString *exclGroupName, ZxElement *dataElem,
332 			   GHash *formValues) {
333   GString *value = getFieldValue(elem, name, fullName, exclGroupName,
334 				 dataElem, formValues);
335   XFAFieldLayoutInfo *layoutInfo = getFieldLayoutInfo(elem);
336   XFAFieldPictureInfo *pictureInfo = getFieldPictureInfo(elem);
337   XFAFieldBarcodeInfo *barcodeInfo = getFieldBarcodeInfo(elem);
338   XFAField *field = new XFAField(name->copy(), fullName->copy(), value,
339 				 layoutInfo, pictureInfo, barcodeInfo);
340   fields->add(field->fullName, field);
341 }
342 
getFieldValue(ZxElement * elem,GString * name,GString * fullName,GString * exclGroupName,ZxElement * dataElem,GHash * formValues)343 GString *XFAScanner::getFieldValue(ZxElement *elem, GString *name,
344 				   GString *fullName, GString *exclGroupName,
345 				   ZxElement *dataElem, GHash *formValues) {
346   GString *val = NULL;
347 
348   //--- check the <xfa:datasets> packet
349   val = getDatasetsValue(name->getCString(), dataElem);
350   if (!val && exclGroupName) {
351     val = (GString *)getDatasetsValue(exclGroupName->getCString(), dataElem);
352   }
353 
354   //--- check the <form> element
355   if (!val) {
356     val = (GString *)formValues->lookup(fullName);
357   }
358 
359   //--- check the <value> element within the field
360   if (!val) {
361     ZxElement *valueElem = elem->findFirstChildElement("value");
362     if (valueElem) {
363       ZxNode *child1Node = valueElem->getFirstChild();
364       if (child1Node && child1Node->isElement()) {
365 	ZxNode *child2Node = ((ZxElement *)child1Node)->getFirstChild();
366 	if (child2Node && child2Node->isCharData()) {
367 	  val = ((ZxCharData *)child2Node)->getData();
368 	}
369       }
370     }
371   }
372 
373   //--- get the checkbutton item value
374   GString *checkbuttonItem = NULL;
375   ZxElement *uiElem = elem->findFirstChildElement("ui");
376   if (uiElem) {
377     ZxNode *uiChild = uiElem->getFirstChild();
378     if (uiChild && uiChild->isElement("checkButton")) {
379       ZxElement *itemsElem = elem->findFirstChildElement("items");
380       if (itemsElem) {
381 	ZxNode *node1 = itemsElem->getFirstChild();
382 	if (node1 && node1->isElement()) {
383 	  ZxNode *node2 = ((ZxElement *)node1)->getFirstChild();
384 	  if (node2 && node2->isCharData()) {
385 	    checkbuttonItem = ((ZxCharData *)node2)->getData();
386 	  }
387 	}
388       }
389     }
390   }
391   // convert XFA checkbutton value to AcroForm-style On/Off value
392   if (checkbuttonItem && val) {
393     if (val->cmp(checkbuttonItem)) {
394       val = new GString("Off");
395     } else {
396       val = new GString("On");
397     }
398   } else if (val) {
399     val = val->copy();
400   }
401 
402   return val;
403 }
404 
getDatasetsValue(char * partName,ZxElement * elem)405 GString *XFAScanner::getDatasetsValue(char *partName, ZxElement *elem) {
406   if (!elem) {
407     return NULL;
408   }
409 
410   // partName = xxxx[nn].yyyy----
411   char *p = strchr(partName, '[');
412   if (!p) {
413     return NULL;
414   }
415   int partLen = (int)(p - partName);
416   int idx = atoi(p + 1);
417   p = strchr(p + 1, '.');
418   if (p) {
419     ++p;
420   }
421 
422   int curIdx = 0;
423   for (ZxNode *node = elem->getFirstChild();
424        node;
425        node = node->getNextChild()) {
426     if (!node->isElement()) {
427       continue;
428     }
429     GString *nodeName = ((ZxElement *)node)->getType();
430     if (nodeName->getLength() != partLen ||
431 	strncmp(nodeName->getCString(), partName, partLen)) {
432       continue;
433     }
434     if (curIdx != idx) {
435       ++curIdx;
436       continue;
437     }
438     if (p) {
439       GString *val = getDatasetsValue(p, (ZxElement *)node);
440       if (val) {
441 	return val;
442       }
443       break;
444     } else {
445       ZxNode *child = ((ZxElement *)node)->getFirstChild();
446       if (!child || !child->isCharData()) {
447 	return NULL;
448       }
449       return ((ZxCharData *)child)->getData();
450     }
451   }
452 
453   // search for an 'ancestor match'
454   if (p) {
455     return getDatasetsValue(p, elem);
456   }
457 
458   return NULL;
459 }
460 
getFieldLayoutInfo(ZxElement * elem)461 XFAFieldLayoutInfo *XFAScanner::getFieldLayoutInfo(ZxElement *elem) {
462   ZxElement *paraElem = elem->findFirstChildElement("para");
463   if (!paraElem) {
464     return NULL;
465   }
466   XFAFieldLayoutHAlign hAlign = xfaFieldLayoutHAlignLeft;
467   ZxAttr *hAlignAttr = paraElem->findAttr("hAlign");
468   if (hAlignAttr) {
469     if (!hAlignAttr->getValue()->cmp("left")) {
470       hAlign = xfaFieldLayoutHAlignLeft;
471     } else if (!hAlignAttr->getValue()->cmp("center")) {
472       hAlign = xfaFieldLayoutHAlignCenter;
473     } else if (!hAlignAttr->getValue()->cmp("right")) {
474       hAlign = xfaFieldLayoutHAlignRight;
475     }
476   }
477   XFAFieldLayoutVAlign vAlign = xfaFieldLayoutVAlignTop;
478   ZxAttr *vAlignAttr = paraElem->findAttr("vAlign");
479   if (vAlignAttr) {
480     if (!vAlignAttr->getValue()->cmp("top")) {
481       vAlign = xfaFieldLayoutVAlignTop;
482     } else if (!vAlignAttr->getValue()->cmp("middle")) {
483       vAlign = xfaFieldLayoutVAlignMiddle;
484     } else if (!vAlignAttr->getValue()->cmp("bottom")) {
485       vAlign = xfaFieldLayoutVAlignBottom;
486     }
487   }
488   return new XFAFieldLayoutInfo(hAlign, vAlign);
489 }
490 
getFieldPictureInfo(ZxElement * elem)491 XFAFieldPictureInfo *XFAScanner::getFieldPictureInfo(ZxElement *elem) {
492   ZxElement *uiElem = elem->findFirstChildElement("ui");
493   if (!uiElem) {
494     return NULL;
495   }
496   XFAFieldPictureSubtype subtype;
497   if (uiElem->findFirstChildElement("dateTimeEdit")) {
498     subtype = xfaFieldPictureDateTime;
499   } else if (uiElem->findFirstChildElement("numericEdit")) {
500     subtype = xfaFieldPictureNumeric;
501   } else if (uiElem->findFirstChildElement("textEdit")) {
502     subtype = xfaFieldPictureText;
503   } else {
504     return NULL;
505   }
506 
507   ZxElement *formatElem, *pictureElem;
508   ZxNode *pictureChildNode;
509   if (!(formatElem = elem->findFirstChildElement("format")) ||
510       !(pictureElem = formatElem->findFirstChildElement("picture")) ||
511       !(pictureChildNode = pictureElem->getFirstChild()) ||
512       !pictureChildNode->isCharData()) {
513     return NULL;
514   }
515   GString *format = ((ZxCharData *)pictureChildNode)->getData()->copy();
516 
517   return new XFAFieldPictureInfo(subtype, format);
518 }
519 
getFieldBarcodeInfo(ZxElement * elem)520 XFAFieldBarcodeInfo *XFAScanner::getFieldBarcodeInfo(ZxElement *elem) {
521   ZxElement *uiElem, *barcodeElem;
522   if (!(uiElem = elem->findFirstChildElement("ui")) ||
523       !(barcodeElem = uiElem->findFirstChildElement("barcode"))) {
524     return NULL;
525   }
526 
527   ZxAttr *attr;
528   if (!(attr = barcodeElem->findAttr("type"))) {
529     return NULL;
530   }
531   GString *barcodeType = attr->getValue()->copy();
532 
533   double wideNarrowRatio = 3;
534   if ((attr = barcodeElem->findAttr("wideNarrowRatio"))) {
535     char *s = attr->getValue()->getCString();
536     char *colon = strchr(s, ':');
537     if (colon) {
538       GString *numStr = new GString(s, (int)(colon - s));
539       double num = atof(numStr->getCString());
540       delete numStr;
541       double den = atof(colon + 1);
542       if (den == 0) {
543 	wideNarrowRatio = num;
544       } else {
545 	wideNarrowRatio = num / den;
546       }
547     } else {
548       wideNarrowRatio = atof(s);
549     }
550   }
551 
552   double moduleWidth = (0.25 / 25.4) * 72.0; // 0.25mm
553   if ((attr = barcodeElem->findAttr("moduleWidth"))) {
554     moduleWidth = getMeasurement(attr->getValue());
555   }
556 
557   double moduleHeight = (5.0 / 25.4) * 72.0; // 5mm
558   if ((attr = barcodeElem->findAttr("moduleHeight"))) {
559     moduleHeight = getMeasurement(attr->getValue());
560   }
561 
562   int dataLength = 0;
563   if ((attr = barcodeElem->findAttr("dataLength"))) {
564     dataLength = atoi(attr->getValue()->getCString());
565   }
566 
567   int errorCorrectionLevel = 0;
568   if ((attr = barcodeElem->findAttr("errorCorrectionLevel"))) {
569     errorCorrectionLevel = atoi(attr->getValue()->getCString());
570   }
571 
572   GString *textLocation;
573   if ((attr = barcodeElem->findAttr("textLocation"))) {
574     textLocation = attr->getValue()->copy();
575   } else {
576     textLocation = new GString("below");
577   }
578 
579   return new XFAFieldBarcodeInfo(barcodeType, wideNarrowRatio,
580 				 moduleWidth, moduleHeight, dataLength,
581 				 errorCorrectionLevel, textLocation);
582 }
583 
getMeasurement(GString * s)584 double XFAScanner::getMeasurement(GString *s) {
585   int i = 0;
586   GBool neg = gFalse;
587   if (i < s->getLength() && s->getChar(i) == '+') {
588     ++i;
589   } else if (i < s->getLength() && s->getChar(i) == '-') {
590     neg = gTrue;
591     ++i;
592   }
593   double val = 0;
594   while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') {
595     val = val * 10 + s->getChar(i) - '0';
596     ++i;
597   }
598   if (i < s->getLength() && s->getChar(i) == '.') {
599     ++i;
600     double mul = 0.1;
601     while (i < s->getLength() &&
602 	   s->getChar(i) >= '0' && s->getChar(i) <= '9') {
603       val += mul * (s->getChar(i) - '0');
604       mul *= 0.1;
605       ++i;
606     }
607   }
608   if (neg) {
609     val = -val;
610   }
611   if (i+1 < s->getLength()) {
612     if (s->getChar(i) == 'i' && s->getChar(i+1) == 'n') {
613       val *= 72;
614     } else if (s->getChar(i) == 'p' && s->getChar(i+1) == 't') {
615       // no change
616     } else if (s->getChar(i) == 'c' && s->getChar(i+1) == 'm') {
617       val *= 72 / 2.54;
618     } else if (s->getChar(i) == 'm' && s->getChar(i+1) == 'm') {
619       val *= 72 / 25.4;
620     } else {
621       // default to inches
622       val *= 72;
623     }
624   } else {
625     // default to inches
626     val *= 72;
627   }
628   return val;
629 }
630 
getNodeName(ZxElement * elem)631 GString *XFAScanner::getNodeName(ZxElement *elem) {
632   if (elem->isElement("template") ||
633       elem->isElement("area") ||
634       elem->isElement("draw")) {
635     return NULL;
636   }
637   if (!elem->isElement("field") && nodeIsBindNone(elem)) {
638     return NULL;
639   }
640   ZxAttr *nameAttr = elem->findAttr("name");
641   if (!nameAttr) {
642     return NULL;
643   }
644   return nameAttr->getValue();
645 }
646 
getNodeFullName(ZxElement * elem)647 GString *XFAScanner::getNodeFullName(ZxElement *elem) {
648   if (elem->isElement("template") ||
649       elem->isElement("draw")) {
650     return NULL;
651   }
652   ZxAttr *nameAttr = elem->findAttr("name");
653   if (!nameAttr) {
654     return NULL;
655   }
656   return nameAttr->getValue();
657 }
658 
nodeIsBindGlobal(ZxElement * elem)659 GBool XFAScanner::nodeIsBindGlobal(ZxElement *elem) {
660   ZxElement *bindElem = elem->findFirstChildElement("bind");
661   if (!bindElem) {
662     return gFalse;
663   }
664   ZxAttr *attr = bindElem->findAttr("match");
665   return attr && !attr->getValue()->cmp("global");
666 }
667 
nodeIsBindNone(ZxElement * elem)668 GBool XFAScanner::nodeIsBindNone(ZxElement *elem) {
669   ZxElement *bindElem = elem->findFirstChildElement("bind");
670   if (!bindElem) {
671     return gFalse;
672   }
673   ZxAttr *attr = bindElem->findAttr("match");
674   return attr && !attr->getValue()->cmp("none");
675 }
676