1 // Copyright (c) 1997 James Clark
2 // See the file copying.txt for copying permission.
3 
4 #include "config.h"
5 #include "TransformFOTBuilder.h"
6 #include "FOTBuilder.h"
7 #include "OutputCharStream.h"
8 #include "MessageArg.h"
9 #include "ErrnoMessageArg.h"
10 
11 #include <errno.h>
12 
13 #ifdef DSSSL_NAMESPACE
14 namespace DSSSL_NAMESPACE {
15 #endif
16 
17 const char RE = '\r';
18 
19 class TransformFOTBuilder : public SerialFOTBuilder {
20 public:
21   // SGML Transformations
22   struct DocumentTypeNIC {
23     ~DocumentTypeNIC();
24     StringC name;
25     StringC publicId;
26     StringC systemId;
27   };
28   struct ElementNIC {
29     ~ElementNIC();
30     StringC gi;
31     Vector<StringC> attributes;
32   };
33   class TransformExtensionFlowObj : public FOTBuilder::ExtensionFlowObj {
34   public:
35     virtual void atomic(TransformFOTBuilder &, const NodePtr &) const = 0;
36   };
37   class TransformCompoundExtensionFlowObj : public FOTBuilder::CompoundExtensionFlowObj {
38   public:
39     virtual void start(TransformFOTBuilder &, const NodePtr &) const = 0;
40     virtual void end(TransformFOTBuilder &) const = 0;
41   };
42   class EntityRefFlowObj : public TransformExtensionFlowObj {
43   public:
EntityRefFlowObj()44     EntityRefFlowObj() {}
atomic(TransformFOTBuilder & fotb,const NodePtr &) const45     void atomic(TransformFOTBuilder &fotb, const NodePtr &) const {
46       fotb.entityRef(name_);
47     }
hasNIC(const StringC & name) const48     bool hasNIC(const StringC &name) const {
49       return name == "name";
50     }
setNIC(const StringC & name,const Value & value)51     void setNIC(const StringC &name, const Value &value) {
52       value.convertString(name_);
53     }
copy() const54     ExtensionFlowObj *copy() const { return new EntityRefFlowObj(*this); }
55   private:
56     StringC name_;
57   };
58   class ProcessingInstructionFlowObj : public TransformExtensionFlowObj {
59   public:
ProcessingInstructionFlowObj()60     ProcessingInstructionFlowObj() {}
atomic(TransformFOTBuilder & fotb,const NodePtr &) const61     void atomic(TransformFOTBuilder &fotb, const NodePtr &) const {
62       fotb.processingInstruction(data_);
63     }
hasNIC(const StringC & name) const64     bool hasNIC(const StringC &name) const {
65       return name.size() == 4 && name[0] == 'd' && name[1] == 'a' && name[2] == 't' && name[3] == 'a';
66     }
setNIC(const StringC & name,const Value & value)67     void setNIC(const StringC &name, const Value &value) {
68       value.convertString(data_);
69     }
copy() const70     ExtensionFlowObj *copy() const { return new ProcessingInstructionFlowObj(*this); }
71   private:
72     StringC data_;
73   };
74   class EmptyElementFlowObj : public TransformExtensionFlowObj {
atomic(TransformFOTBuilder & fotb,const NodePtr & nd) const75     void atomic(TransformFOTBuilder &fotb, const NodePtr &nd) const {
76       if (nic_.gi.size() > 0)
77 	fotb.emptyElement(nic_);
78       else {
79 	GroveString str;
80 	if (nd && nd->getGi(str) == accessOK) {
81 	  ElementNIC tem(nic_);
82 	  tem.gi.assign(str.data(), str.size());
83 	  fotb.emptyElement(tem);
84 	}
85 	else
86 	  fotb.emptyElement(nic_);
87       }
88     }
hasNIC(const StringC & name) const89     bool hasNIC(const StringC &name) const {
90       return name == "gi" || name == "attributes";
91     }
setNIC(const StringC & name,const Value & value)92     void setNIC(const StringC &name, const Value &value) {
93       switch (name[0]) {
94       case 'g':
95 	value.convertString(nic_.gi);
96 	break;
97       case 'a':
98 	value.convertStringPairList(nic_.attributes);
99 	break;
100       }
101     }
copy() const102     ExtensionFlowObj *copy() const { return new EmptyElementFlowObj(*this); }
103   public:
EmptyElementFlowObj()104     EmptyElementFlowObj() {}
105   private:
106     ElementNIC nic_;
107   };
108   class ElementFlowObj : public TransformCompoundExtensionFlowObj {
start(TransformFOTBuilder & fotb,const NodePtr & nd) const109     void start(TransformFOTBuilder &fotb, const NodePtr &nd) const {
110       if (nic_.gi.size() > 0)
111 	fotb.startElement(nic_);
112       else {
113 	GroveString str;
114 	if (nd && nd->getGi(str) == accessOK) {
115 	  ElementNIC tem(nic_);
116 	  tem.gi.assign(str.data(), str.size());
117 	  fotb.startElement(tem);
118 	}
119 	else
120 	  fotb.startElement(nic_);
121       }
122     }
end(TransformFOTBuilder & fotb) const123     void end(TransformFOTBuilder &fotb) const {
124       fotb.endElement();
125     }
hasNIC(const StringC & name) const126     bool hasNIC(const StringC &name) const {
127       return name == "gi" || name == "attributes";
128     }
setNIC(const StringC & name,const Value & value)129     void setNIC(const StringC &name, const Value &value) {
130       switch (name[0]) {
131       case 'g':
132 	value.convertString(nic_.gi);
133 	break;
134       case 'a':
135 	value.convertStringPairList(nic_.attributes);
136 	break;
137       }
138     }
copy() const139     ExtensionFlowObj *copy() const { return new ElementFlowObj(*this); }
140   public:
ElementFlowObj()141     ElementFlowObj() {}
142   private:
143     ElementNIC nic_;
144   };
145   class EntityFlowObj : public TransformCompoundExtensionFlowObj {
start(TransformFOTBuilder & fotb,const NodePtr &) const146     void start(TransformFOTBuilder &fotb, const NodePtr &) const {
147       fotb.startEntity(systemId_);
148     }
end(TransformFOTBuilder & fotb) const149     void end(TransformFOTBuilder &fotb) const {
150       fotb.endEntity();
151     }
hasNIC(const StringC & name) const152     bool hasNIC(const StringC &name) const {
153       return name == "system-id";
154     }
setNIC(const StringC & name,const Value & value)155     void setNIC(const StringC &name, const Value &value) {
156       value.convertString(systemId_);
157     }
copy() const158     ExtensionFlowObj *copy() const { return new EntityFlowObj(*this); }
159   public:
EntityFlowObj()160     EntityFlowObj() {};
161   private:
162     StringC systemId_;
163   };
164   class DocumentTypeFlowObj : public TransformExtensionFlowObj {
atomic(TransformFOTBuilder & fotb,const NodePtr & nd) const165     void atomic(TransformFOTBuilder &fotb, const NodePtr &nd) const {
166       fotb.documentType(nic_);
167     }
hasNIC(const StringC & name) const168     bool hasNIC(const StringC &name) const {
169       return name == "system-id" || name == "public-id" || name == "name";
170     }
setNIC(const StringC & name,const Value & value)171     void setNIC(const StringC &name, const Value &value) {
172       switch (name[0]) {
173       case 's':
174 	value.convertString(nic_.systemId);
175 	break;
176       case 'p':
177 	value.convertString(nic_.publicId);
178 	break;
179       case 'n':
180 	value.convertString(nic_.name);
181 	break;
182       }
183     }
copy() const184     ExtensionFlowObj *copy() const { return new DocumentTypeFlowObj(*this); }
185   public:
DocumentTypeFlowObj()186     DocumentTypeFlowObj() {}
187   private:
188     DocumentTypeNIC nic_;
189   };
190   TransformFOTBuilder(CmdLineApp *, bool xml, const Vector<StringC> &options);
191   ~TransformFOTBuilder();
192   void startElement(const ElementNIC &);
193   void endElement();
194   void emptyElement(const ElementNIC &);
195   void characters(const Char *s, size_t n);
196   void charactersFromNode(const NodePtr &, const Char *, size_t);
197   void processingInstruction(const StringC &);
198   void documentType(const DocumentTypeNIC &);
199   void formattingInstruction(const StringC &);
200   void entityRef(const StringC &);
201   void startEntity(const StringC &);
202   void endEntity();
203   void extension(const ExtensionFlowObj &fo, const NodePtr &);
204   void startExtensionSerial(const CompoundExtensionFlowObj &fo, const NodePtr &nd);
205   void endExtensionSerial(const CompoundExtensionFlowObj &fo);
206   void start();
207   void end();
208   void setPreserveSdata(bool);
209 private:
210   TransformFOTBuilder(const TransformFOTBuilder &);
211   void operator=(const TransformFOTBuilder &);
212 
os()213   OutputCharStream &os() { return *os_; }
214   void attributes(const Vector<StringC> &atts);
flushPendingRe()215   void flushPendingRe() {
216     if (state_ == statePendingRe) {
217       os() << RE;
218       state_ = stateMiddle;
219     }
220   }
flushPendingReCharRef()221   void flushPendingReCharRef() {
222     if (state_ == statePendingRe) {
223       os() << "&#13;";
224       state_ = stateMiddle;
225     }
226   }
227 
228   CmdLineApp *app_;
229   OutputCharStream *os_;
230   Owner<OutputCharStream> topOs_;
231   Vector<StringC> openElements_;
232   StringC undefGi_;
233   struct OpenFile : Link {
234     ~OpenFile();
235     OutputCharStream *saveOs;
236     // fb must be before os so it gets destroyed afterwards
237     FileOutputByteStream fb;
238     Owner<OutputCharStream> os;
239     StringC systemId;
240   };
241   IList<OpenFile> openFileStack_;
242   bool xml_;
243   enum ReState {
244     stateMiddle,
245     stateStartOfElement,
246     statePendingRe
247   };
248   ReState state_;
249   bool preserveSdata_;
250   char RE_[2];
251   char SP_[2];
252   // Really Vector<bool>
253   StringC preserveSdataStack_;
254 };
255 
makeTransformFOTBuilder(CmdLineApp * app,bool xml,const Vector<StringC> & options,const FOTBuilder::Extension * & ext)256 FOTBuilder *makeTransformFOTBuilder(CmdLineApp *app,
257 				    bool xml,
258 				    const Vector<StringC> &options,
259 				    const FOTBuilder::Extension *&ext)
260 {
261   static const TransformFOTBuilder::ProcessingInstructionFlowObj pi;
262   static const TransformFOTBuilder::ElementFlowObj element;
263   static const TransformFOTBuilder::EmptyElementFlowObj emptyElement;
264   static const TransformFOTBuilder::EntityFlowObj entity;
265   static const TransformFOTBuilder::EntityRefFlowObj entityRef;
266   static const TransformFOTBuilder::DocumentTypeFlowObj documentType;
267   static const FOTBuilder::Extension extensions[] = {
268     {
269       "UNREGISTERED::James Clark//Flow Object Class::processing-instruction",
270       0,
271       0,
272       0,
273       0,
274       &pi
275     },
276     {
277       "UNREGISTERED::James Clark//Flow Object Class::element",
278       0,
279       0,
280       0,
281       0,
282       &element
283     },
284     {
285       "UNREGISTERED::James Clark//Flow Object Class::empty-element",
286       0,
287       0,
288       0,
289       0,
290       &emptyElement
291     },
292     {
293       "UNREGISTERED::James Clark//Flow Object Class::entity",
294       0,
295       0,
296       0,
297       0,
298       &entity
299     },
300     {
301       "UNREGISTERED::James Clark//Flow Object Class::entity-ref",
302       0,
303       0,
304       0,
305       0,
306       &entityRef
307     },
308     {
309       "UNREGISTERED::James Clark//Flow Object Class::document-type",
310       0,
311       0,
312       0,
313       0,
314       &documentType
315     },
316     {
317       "UNREGISTERED::James Clark//Characteristic::preserve-sdata?",
318       (void (FOTBuilder::*)(bool))&TransformFOTBuilder::setPreserveSdata,
319       0,
320       0,
321       0,
322       0
323     },
324     { 0 }
325   };
326   ext = extensions;
327   return new TransformFOTBuilder(app, xml, options);
328 }
329 
330 static
outputNumericCharRef(OutputCharStream & os,Char c)331 void outputNumericCharRef(OutputCharStream &os, Char c)
332 {
333   os << "&#" << (unsigned long)c << ';';
334 }
335 
TransformFOTBuilder(CmdLineApp * app,bool xml,const Vector<StringC> & options)336 TransformFOTBuilder::TransformFOTBuilder(CmdLineApp *app, bool xml,
337 					 const Vector<StringC> &options)
338 : app_(app),
339   xml_(xml),
340   topOs_(new RecordOutputCharStream(app->makeStdOut())),
341   state_(stateMiddle),
342   preserveSdata_(0)
343 {
344   undefGi_ = app_->systemCharset().execToDesc("#UNDEF");
345   topOs_->setEscaper(outputNumericCharRef);
346   os_ = topOs_.pointer();
347   preserveSdataStack_ += 0;
348   RE_[0] = RE;
349   RE_[1] = 0;
350   SP_[0] = RE;
351   SP_[1] = 0;
352   for (size_t i = 0; i < options.size(); i++) {
353     if (options[i] == app_->systemCharset().execToDesc("raw")) {
354       RE_[0] = 0;
355       SP_[0] = ' ';
356     }
357   }
358 }
359 
~TransformFOTBuilder()360 TransformFOTBuilder::~TransformFOTBuilder()
361 {
362 }
363 
contains(const StringC & str,Char c)364 static bool contains(const StringC &str, Char c)
365 {
366   for (size_t i = 0; i < str.size(); i++)
367     if (str[i] == c)
368        return 1;
369   return 0;
370 }
371 
documentType(const DocumentTypeNIC & nic)372 void TransformFOTBuilder::documentType(const DocumentTypeNIC &nic)
373 {
374   flushPendingRe();
375   if (nic.name.size()) {
376     os() << "<!DOCTYPE " << nic.name;
377     if (nic.publicId.size())
378       os() << " PUBLIC \"" << nic.publicId << '"';
379     else
380       os() << " SYSTEM";
381     if (nic.systemId.size()) {
382       if (nic.publicId.size()) {
383 	os() << ' ';
384       }
385       char quote = contains(nic.systemId, '"') ? '\'' : '"';
386       os() << quote << nic.systemId << quote;
387     }
388     os() << '>' << RE;
389   }
390   atomic();
391 }
392 
attributes(const Vector<StringC> & atts)393 void TransformFOTBuilder::attributes(const Vector<StringC> &atts)
394 {
395   for (size_t i = 0; i < atts.size(); i += 2) {
396     os() << SP_ << atts[i] << '=';
397     const StringC &s = atts[i + 1];
398     char quoteChar = 0;
399 
400     if (!contains(s, '&'))
401       if (!contains(s, '"'))
402         quoteChar = '"';
403       else if (!contains(s, '\''))
404         quoteChar = '\'';
405 
406     if (quoteChar)
407       os() << quoteChar << s << quoteChar;
408     else {
409       os() << '"';
410       for (size_t j = 0; j < s.size(); j++) {
411         if (s[j] == '"') {
412 	  if (xml_)
413 	    os() << "&quot;";
414 	  else
415 	    outputNumericCharRef(os(), '"');
416 	}
417 	else
418 	if (s[j] == '&' ) {
419           if (xml_)
420             os() << "&amp;";
421 	  else
422             outputNumericCharRef(os(), '&');
423 	}
424         else
425           os().put(s[j]);
426       }
427       os() << '"';
428     }
429   }
430 }
431 
startElement(const ElementNIC & nic)432 void TransformFOTBuilder::startElement(const ElementNIC &nic)
433 {
434   flushPendingRe();
435   os() << "<";
436   const StringC &s = nic.gi.size() == 0 ? undefGi_ : nic.gi;
437   os() << s;
438   attributes(nic.attributes);
439   os() << RE_ << '>';
440   openElements_.push_back(s);
441   start();
442   state_ = stateStartOfElement;
443 }
444 
emptyElement(const ElementNIC & nic)445 void TransformFOTBuilder::emptyElement(const ElementNIC &nic)
446 {
447   flushPendingRe();
448   os() << "<";
449   const StringC &s = nic.gi.size() == 0 ? undefGi_ : nic.gi;
450   os() << s;
451   attributes(nic.attributes);
452   if (xml_)
453     os() << "/>";
454   else
455     os() << '>';
456   atomic();
457   state_ = stateMiddle;
458 }
459 
endElement()460 void TransformFOTBuilder::endElement()
461 {
462   flushPendingReCharRef();
463   os() << "</" << openElements_.back();
464   os() << RE_ << '>';
465   openElements_.resize(openElements_.size() - 1);
466   end();
467   state_ = stateMiddle;
468 }
469 
processingInstruction(const StringC & s)470 void TransformFOTBuilder::processingInstruction(const StringC &s)
471 {
472   flushPendingReCharRef();
473   os() << "<?" << s;
474   if (xml_)
475     os() << "?>";
476   else
477     os() << '>';
478   atomic();
479 }
480 
formattingInstruction(const StringC & s)481 void TransformFOTBuilder::formattingInstruction(const StringC &s)
482 {
483   flushPendingRe();
484   os() << s;
485 }
486 
entityRef(const StringC & s)487 void TransformFOTBuilder::entityRef(const StringC &s)
488 {
489   flushPendingRe();
490   os() << "&" << s << ";";
491 }
492 
startEntity(const StringC & systemId)493 void TransformFOTBuilder::startEntity(const StringC &systemId)
494 {
495   flushPendingRe();
496   OpenFile *ofp = new OpenFile;
497   openFileStack_.insert(ofp);
498   ofp->systemId = systemId;
499   ofp->saveOs = os_;
500   String<CmdLineApp::AppChar> filename;
501 #ifdef SP_WIDE_SYSTEM
502   filename = systemId;
503 #else
504   filename = app_->codingSystem()->convertOut(systemId);
505 #endif
506   if (filename.size()) {
507     filename += 0;
508     if (!ofp->fb.open(filename.data())) {
509       app_->message(CmdLineApp::openFileErrorMessage(),
510 		    StringMessageArg(systemId),
511 		    ErrnoMessageArg(errno));
512     }
513     else {
514       ofp->os
515 	= new RecordOutputCharStream(
516 	      new EncodeOutputCharStream(&ofp->fb,
517 				         app_->outputCodingSystem()));
518       ofp->os->setEscaper(outputNumericCharRef);
519       os_ = ofp->os.pointer();
520     }
521   }
522 }
523 
endEntity()524 void TransformFOTBuilder::endEntity()
525 {
526   flushPendingRe();
527   OpenFile &of = *openFileStack_.head();
528   if (of.os) {
529     errno = 0;
530     of.os->flush();
531     if (!of.fb.close())
532       app_->message(CmdLineApp::closeFileErrorMessage(),
533 		    StringMessageArg(of.systemId),
534 		    ErrnoMessageArg(errno));
535   }
536   os_ = of.saveOs;
537   delete openFileStack_.get();
538 }
539 
540 inline
operator <<(OutputCharStream & os,GroveString & str)541 OutputCharStream &operator<<(OutputCharStream &os, GroveString &str)
542 {
543   return os.write(str.data(), str.size());
544 }
545 
charactersFromNode(const NodePtr & nd,const Char * s,size_t n)546 void TransformFOTBuilder::charactersFromNode(const NodePtr &nd, const Char *s, size_t n)
547 {
548   GroveString name;
549   if (preserveSdata_ && n == 1 && nd->getEntityName(name) == accessOK) {
550     flushPendingRe();
551     os() << "&" << name << ';';
552   }
553   else
554     TransformFOTBuilder::characters(s, n);
555 }
556 
characters(const Char * s,size_t n)557 void TransformFOTBuilder::characters(const Char *s, size_t n)
558 {
559   if (n == 0)
560     return;
561   flushPendingRe();
562   if (state_ == stateStartOfElement && *s == RE) {
563     s++;
564     n--;
565     os() << "&#13;";
566     if (n == 0) {
567       state_ = stateMiddle;
568       return;
569     }
570   }
571   if (s[n - 1] == RE) {
572     n--;
573     state_ = statePendingRe;
574   }
575   else
576     state_ = stateMiddle;
577   for (; n > 0; n--, s++) {
578     switch (*s) {
579     case '&':
580       if (xml_)
581 	os() << "&amp;";
582       else
583 	outputNumericCharRef(os(), *s);
584       break;
585     case '<':
586       if (xml_)
587 	os() << "&lt;";
588       else
589 	outputNumericCharRef(os(), *s);
590       break;
591     case '>':
592       if (xml_)
593 	os() << "&gt;";
594       else
595 	outputNumericCharRef(os(), *s);
596       break;
597     default:
598       os().put(*s);
599       break;
600     }
601   }
602 }
603 
extension(const ExtensionFlowObj & fo,const NodePtr & nd)604 void TransformFOTBuilder::extension(const ExtensionFlowObj &fo, const NodePtr &nd)
605 {
606   ((const TransformExtensionFlowObj &)fo).atomic(*this, nd);
607 }
608 
startExtensionSerial(const CompoundExtensionFlowObj & fo,const NodePtr & nd)609 void TransformFOTBuilder::startExtensionSerial(const CompoundExtensionFlowObj &fo, const NodePtr &nd)
610 {
611   ((const TransformCompoundExtensionFlowObj &)fo).start(*this, nd);
612 }
613 
endExtensionSerial(const CompoundExtensionFlowObj & fo)614 void TransformFOTBuilder::endExtensionSerial(const CompoundExtensionFlowObj &fo)
615 {
616   ((const TransformCompoundExtensionFlowObj &)fo).end(*this);
617 }
618 
setPreserveSdata(bool b)619 void TransformFOTBuilder::setPreserveSdata(bool b)
620 {
621   preserveSdata_ = b;
622 }
623 
start()624 void TransformFOTBuilder::start()
625 {
626   preserveSdataStack_ += Char(preserveSdata_);
627 }
628 
end()629 void TransformFOTBuilder::end()
630 {
631   preserveSdataStack_.resize(preserveSdataStack_.size() - 1);
632   preserveSdata_ = bool(preserveSdataStack_[preserveSdataStack_.size() - 1]);
633 }
634 
~OpenFile()635 TransformFOTBuilder::OpenFile::~OpenFile()
636 {
637 }
638 
~DocumentTypeNIC()639 TransformFOTBuilder::DocumentTypeNIC::~DocumentTypeNIC()
640 {
641 }
642 
~ElementNIC()643 TransformFOTBuilder::ElementNIC::~ElementNIC()
644 {
645 }
646 
647 #ifdef DSSSL_NAMESPACE
648 }
649 #endif
650 
651 #include "TransformFOTBuilder_inst.cxx"
652