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() << " ";
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() << """;
414 else
415 outputNumericCharRef(os(), '"');
416 }
417 else
418 if (s[j] == '&' ) {
419 if (xml_)
420 os() << "&";
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() << " ";
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() << "&";
582 else
583 outputNumericCharRef(os(), *s);
584 break;
585 case '<':
586 if (xml_)
587 os() << "<";
588 else
589 outputNumericCharRef(os(), *s);
590 break;
591 case '>':
592 if (xml_)
593 os() << ">";
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