1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 #include "OOXMLPropertySetImpl.hxx"
25 #include <stdio.h>
26 #include <iostream>
27 #include <resourcemodel/QNameToString.hxx>
28 #include <resourcemodel/Protocol.hxx>
29 #include <com/sun/star/drawing/XShape.hpp>
30 #include <ooxml/OOXMLFastTokens.hxx>
31 #include "ooxmlLoggers.hxx"
32
33 namespace writerfilter {
34 namespace ooxml
35 {
36 using namespace ::std;
37
38 static ::rtl::OUString strue(RTL_CONSTASCII_USTRINGPARAM("true"));
39 static ::rtl::OUString sTrue(RTL_CONSTASCII_USTRINGPARAM("True"));
40 static ::rtl::OUString s1(RTL_CONSTASCII_USTRINGPARAM("1"));
41 static ::rtl::OUString sOn(RTL_CONSTASCII_USTRINGPARAM("On"));
42 static ::rtl::OUString son(RTL_CONSTASCII_USTRINGPARAM("on"));
43
~OOXMLProperty()44 OOXMLProperty::~OOXMLProperty()
45 {
46 }
47
~OOXMLPropertySet()48 OOXMLPropertySet::~OOXMLPropertySet()
49 {
50 }
51
~OOXMLTable()52 OOXMLTable::~OOXMLTable()
53 {
54 }
55
OOXMLPropertyImpl(Id id,OOXMLValue::Pointer_t pValue,OOXMLPropertyImpl::Type_t eType)56 OOXMLPropertyImpl::OOXMLPropertyImpl(Id id, OOXMLValue::Pointer_t pValue,
57 OOXMLPropertyImpl::Type_t eType)
58 : mId(id), mpValue(pValue), meType(eType)
59 {
60 }
61
OOXMLPropertyImpl(const OOXMLPropertyImpl & rSprm)62 OOXMLPropertyImpl::OOXMLPropertyImpl(const OOXMLPropertyImpl & rSprm)
63 : OOXMLProperty(), mId(rSprm.mId), mpValue(rSprm.mpValue), meType(rSprm.meType)
64 {
65 }
66
~OOXMLPropertyImpl()67 OOXMLPropertyImpl::~OOXMLPropertyImpl()
68 {
69 }
70
getId() const71 sal_uInt32 OOXMLPropertyImpl::getId() const
72 {
73 return mId;
74 }
75
getValue()76 Value::Pointer_t OOXMLPropertyImpl::getValue()
77 {
78 Value::Pointer_t pResult;
79
80 if (mpValue.get() != NULL)
81 pResult = Value::Pointer_t(mpValue->clone());
82 else
83 pResult = Value::Pointer_t(new OOXMLValue());
84
85 return pResult;
86 }
87
getBinary()88 writerfilter::Reference<BinaryObj>::Pointer_t OOXMLPropertyImpl::getBinary()
89 {
90 writerfilter::Reference<BinaryObj>::Pointer_t pResult;
91
92 if (mpValue.get() != NULL)
93 pResult = mpValue->getBinary();
94
95 return pResult;
96 }
97
getStream()98 writerfilter::Reference<Stream>::Pointer_t OOXMLPropertyImpl::getStream()
99 {
100 writerfilter::Reference<Stream>::Pointer_t pResult;
101
102 if (mpValue.get() != NULL)
103 pResult = mpValue->getStream();
104
105 return pResult;
106 }
107
getProps()108 writerfilter::Reference<Properties>::Pointer_t OOXMLPropertyImpl::getProps()
109 {
110 writerfilter::Reference<Properties>::Pointer_t pResult;
111
112 if (mpValue.get() != NULL)
113 pResult = mpValue->getProperties();
114
115 return pResult;
116 }
117
getName() const118 string OOXMLPropertyImpl::getName() const
119 {
120 string sResult = (*QNameToString::Instance())(mId);
121
122 if (sResult.length() == 0)
123 sResult = (*SprmIdToString::Instance())(mId);
124
125 if (sResult.length() == 0)
126 sResult = fastTokenToId(mId);
127
128 if (sResult.length() == 0)
129 {
130 static char sBuffer[256];
131
132 snprintf(sBuffer, sizeof(sBuffer), "%" SAL_PRIxUINT32, mId);
133 sResult = sBuffer;
134 }
135
136 return sResult;
137 }
138
toString() const139 string OOXMLPropertyImpl::toString() const
140 {
141 string sResult = "(";
142
143 sResult += getName();
144 sResult += ", ";
145 if (mpValue.get() != NULL)
146 sResult += mpValue->toString();
147 else
148 sResult +="(null)";
149 sResult +=")";
150
151 return sResult;
152 }
153
getKind()154 Sprm::Kind OOXMLPropertyImpl::getKind()
155 {
156 return SprmKind(getId());
157 }
158
clone()159 Sprm * OOXMLPropertyImpl::clone()
160 {
161 return new OOXMLPropertyImpl(*this);
162 }
163
resolve(writerfilter::Properties & rProperties)164 void OOXMLPropertyImpl::resolve(writerfilter::Properties & rProperties)
165 {
166 writerfilter::Properties * pProperties = NULL;
167 #ifdef DEBUG_PROTOCOL
168 writerfilter::PropertiesProtocol::Pointer_t pProtocol
169 (new writerfilter::PropertiesProtocol(&rProperties, debug_logger));
170 pProperties = pProtocol.get();
171 #else
172 pProperties = &rProperties;
173 #endif
174
175 switch (meType)
176 {
177 case SPRM:
178 if (mId != 0x0)
179 pProperties->sprm(*this);
180 break;
181 case ATTRIBUTE:
182 pProperties->attribute(mId, *getValue());
183 break;
184 }
185 }
186
187 /*
188 class OOXMLValue
189 */
190
OOXMLValue()191 OOXMLValue::OOXMLValue()
192 {
193 }
194
~OOXMLValue()195 OOXMLValue::~OOXMLValue()
196 {
197 }
198
getBool() const199 bool OOXMLValue::getBool() const
200 {
201 return false;
202 }
203
getInt() const204 sal_Int32 OOXMLValue::getInt() const
205 {
206 return 0;
207 }
208
getString() const209 ::rtl::OUString OOXMLValue::getString() const
210 {
211 return ::rtl::OUString();
212 }
213
getAny() const214 uno::Any OOXMLValue::getAny() const
215 {
216 return uno::Any();
217 }
218
getProperties()219 writerfilter::Reference<Properties>::Pointer_t OOXMLValue::getProperties()
220 {
221 return writerfilter::Reference<Properties>::Pointer_t();
222 }
223
getStream()224 writerfilter::Reference<Stream>::Pointer_t OOXMLValue::getStream()
225 {
226 return writerfilter::Reference<Stream>::Pointer_t();
227 }
228
getBinary()229 writerfilter::Reference<BinaryObj>::Pointer_t OOXMLValue::getBinary()
230 {
231 return writerfilter::Reference<BinaryObj>::Pointer_t();
232 }
233
toString() const234 string OOXMLValue::toString() const
235 {
236 return "OOXMLValue";
237 }
238
clone() const239 OOXMLValue * OOXMLValue::clone() const
240 {
241 return new OOXMLValue(*this);
242 }
243
244 /*
245 class OOXMLBinaryValue
246 */
247
OOXMLBinaryValue(OOXMLBinaryObjectReference::Pointer_t pBinaryObj)248 OOXMLBinaryValue::OOXMLBinaryValue(OOXMLBinaryObjectReference::Pointer_t
249 pBinaryObj)
250 : mpBinaryObj(pBinaryObj)
251 {
252 }
253
~OOXMLBinaryValue()254 OOXMLBinaryValue::~OOXMLBinaryValue()
255 {
256 }
257
getBinary()258 writerfilter::Reference<BinaryObj>::Pointer_t OOXMLBinaryValue::getBinary()
259 {
260 return mpBinaryObj;
261 }
262
toString() const263 string OOXMLBinaryValue::toString() const
264 {
265 return "BinaryObj";
266 }
267
clone() const268 OOXMLValue * OOXMLBinaryValue::clone() const
269 {
270 return new OOXMLBinaryValue(mpBinaryObj);
271 }
272
273 /*
274 class OOXMLBooleanValue
275 */
276
OOXMLBooleanValue(bool bValue)277 OOXMLBooleanValue::OOXMLBooleanValue(bool bValue)
278 : mbValue(bValue)
279 {
280 }
281
OOXMLBooleanValue(const rtl::OUString & rValue)282 OOXMLBooleanValue::OOXMLBooleanValue(const rtl::OUString & rValue)
283 : mbValue(false)
284 {
285 if (strue.compareTo(rValue) == 0
286 || sTrue.compareTo(rValue) == 0
287 || s1.compareTo(rValue) == 0
288 || son.compareTo(rValue) == 0
289 || sOn.compareTo(rValue) == 0)
290 mbValue = true;
291 else
292 mbValue = false;
293 }
294
~OOXMLBooleanValue()295 OOXMLBooleanValue::~OOXMLBooleanValue()
296 {
297 }
298
getBool() const299 bool OOXMLBooleanValue::getBool() const
300 {
301 return mbValue;
302 }
303
getInt() const304 sal_Int32 OOXMLBooleanValue::getInt() const
305 {
306 return mbValue ? 1 : 0;
307 }
308
getAny() const309 uno::Any OOXMLBooleanValue::getAny() const
310 {
311 uno::Any aResult(mbValue);
312
313 return aResult;
314 }
315
toString() const316 string OOXMLBooleanValue::toString() const
317 {
318 return mbValue ? "true" : "false";
319 }
320
clone() const321 OOXMLValue * OOXMLBooleanValue::clone() const
322 {
323 return new OOXMLBooleanValue(*this);
324 }
325
326 /*
327 class OOXMLStringValue
328 */
329
OOXMLStringValue(const rtl::OUString & rStr)330 OOXMLStringValue::OOXMLStringValue(const rtl::OUString & rStr)
331 : mStr(rStr)
332 {
333 }
334
~OOXMLStringValue()335 OOXMLStringValue::~OOXMLStringValue()
336 {
337 }
338
getAny() const339 uno::Any OOXMLStringValue::getAny() const
340 {
341 uno::Any aAny(mStr);
342
343 return aAny;
344 }
345
getString() const346 rtl::OUString OOXMLStringValue::getString() const
347 {
348 return mStr;
349 }
350
toString() const351 string OOXMLStringValue::toString() const
352 {
353 return OUStringToOString(mStr, RTL_TEXTENCODING_ASCII_US).getStr();
354 }
355
clone() const356 OOXMLValue * OOXMLStringValue::clone() const
357 {
358 return new OOXMLStringValue(*this);
359 }
360
361 /*
362 class OOXMLInputStreamValue
363 */
OOXMLInputStreamValue(uno::Reference<io::XInputStream> xInputStream)364 OOXMLInputStreamValue::OOXMLInputStreamValue(uno::Reference<io::XInputStream> xInputStream)
365 : mxInputStream(xInputStream)
366 {
367 }
368
~OOXMLInputStreamValue()369 OOXMLInputStreamValue::~OOXMLInputStreamValue()
370 {
371 }
372
getAny() const373 uno::Any OOXMLInputStreamValue::getAny() const
374 {
375 uno::Any aAny(mxInputStream);
376
377 return aAny;
378 }
379
toString() const380 string OOXMLInputStreamValue::toString() const
381 {
382 return "InputStream";
383 }
384
clone() const385 OOXMLValue * OOXMLInputStreamValue::clone() const
386 {
387 return new OOXMLInputStreamValue(mxInputStream);
388 }
389
390 /*
391 struct OOXMLPropertySetImplCompare
392 */
393
operator ()(const OOXMLProperty::Pointer_t x,const OOXMLProperty::Pointer_t y) const394 bool OOXMLPropertySetImplCompare::operator()(const OOXMLProperty::Pointer_t x,
395 const OOXMLProperty::Pointer_t y) const
396 {
397 bool bResult = false;
398
399 if (x.get() == NULL && y.get() != NULL)
400 bResult = true;
401 else if (x.get() != NULL && y.get() != NULL)
402 bResult = x->getId() < y->getId();
403
404 return bResult;
405 }
406
407 /**
408 class OOXMLPropertySetImpl
409 */
410
OOXMLPropertySetImpl()411 OOXMLPropertySetImpl::OOXMLPropertySetImpl()
412 : msType("OOXMLPropertySetImpl")
413 {
414 }
415
~OOXMLPropertySetImpl()416 OOXMLPropertySetImpl::~OOXMLPropertySetImpl()
417 {
418 }
419
resolve(Properties & rHandler)420 void OOXMLPropertySetImpl::resolve(Properties & rHandler)
421 {
422 OOXMLProperties_t::iterator aIt = begin();
423 while (aIt != end())
424 {
425 OOXMLProperty::Pointer_t pProp = *aIt;
426
427 if (pProp.get() != NULL)
428 pProp->resolve(rHandler);
429 #ifdef DEBUG_RESOLVE
430 else
431 {
432 debug_logger->startElement("error");
433 debug_logger->chars("zero-property");
434 debug_logger->endElement("error");
435 }
436 #endif
437
438 aIt++;
439 }
440 }
441
begin()442 OOXMLPropertySetImpl::OOXMLProperties_t::iterator OOXMLPropertySetImpl::begin()
443 {
444 return mProperties.begin();
445 }
446
end()447 OOXMLPropertySetImpl::OOXMLProperties_t::iterator OOXMLPropertySetImpl::end()
448 {
449 return mProperties.end();
450 }
451
452 OOXMLPropertySetImpl::OOXMLProperties_t::const_iterator
begin() const453 OOXMLPropertySetImpl::begin() const
454 {
455 return mProperties.begin();
456 }
457
458 OOXMLPropertySetImpl::OOXMLProperties_t::const_iterator
end() const459 OOXMLPropertySetImpl::end() const
460 {
461 return mProperties.end();
462 }
463
getType() const464 string OOXMLPropertySetImpl::getType() const
465 {
466 return msType;
467 }
468
add(OOXMLProperty::Pointer_t pProperty)469 void OOXMLPropertySetImpl::add(OOXMLProperty::Pointer_t pProperty)
470 {
471 #ifdef DEBUG_PROPERTY_SET
472 debug_logger->startElement("propertyset.add");
473 debug_logger->chars(pProperty->toString());
474 #endif
475
476 if (pProperty.get() != NULL && pProperty->getId() != 0x0)
477 {
478 mProperties.push_back(pProperty);
479 }
480 #ifdef DEBUG_PROPERTY_SET
481 else
482 {
483 debug_logger->element("warning.property_not_added");
484 }
485
486 debug_logger->endElement("propertyset.add");
487 #endif
488 }
489
add(OOXMLPropertySet::Pointer_t pPropertySet)490 void OOXMLPropertySetImpl::add(OOXMLPropertySet::Pointer_t pPropertySet)
491 {
492 if (pPropertySet.get() != NULL)
493 {
494 OOXMLPropertySetImpl * pSet =
495 dynamic_cast<OOXMLPropertySetImpl *>(pPropertySet.get());
496
497 if (pSet != NULL)
498 {
499 mProperties.resize(mProperties.size() + pSet->mProperties.size());
500 for (OOXMLProperties_t::iterator aIt = pSet->mProperties.begin();
501 aIt != pSet->mProperties.end(); aIt++)
502 add(*aIt);
503 }
504 }
505 }
506
clone() const507 OOXMLPropertySet * OOXMLPropertySetImpl::clone() const
508 {
509 return new OOXMLPropertySetImpl(*this);
510 }
511
setType(const string & rsType)512 void OOXMLPropertySetImpl::setType(const string & rsType)
513 {
514 msType = rsType;
515 }
516
toString()517 string OOXMLPropertySetImpl::toString()
518 {
519 string sResult = "[";
520 char sBuffer[256];
521 snprintf(sBuffer, sizeof(sBuffer), "%p", this);
522 sResult += sBuffer;
523 sResult += ":";
524
525 OOXMLProperties_t::iterator aItBegin = begin();
526 OOXMLProperties_t::iterator aItEnd = end();
527
528 for (OOXMLProperties_t::iterator aIt = aItBegin; aIt != aItEnd; aIt++)
529 {
530 if (aIt != aItBegin)
531 sResult += ", ";
532
533 if ((*aIt).get() != NULL)
534 sResult += (*aIt)->toString();
535 else
536 sResult += "0x0";
537 }
538
539 sResult += "]";
540
541 return sResult;
542 }
543
544 /*
545 class OOXMLPropertySetValue
546 */
547
OOXMLPropertySetValue(OOXMLPropertySet::Pointer_t pPropertySet)548 OOXMLPropertySetValue::OOXMLPropertySetValue
549 (OOXMLPropertySet::Pointer_t pPropertySet)
550 : mpPropertySet(pPropertySet)
551 {
552 }
553
~OOXMLPropertySetValue()554 OOXMLPropertySetValue::~OOXMLPropertySetValue()
555 {
556 }
557
getProperties()558 writerfilter::Reference<Properties>::Pointer_t OOXMLPropertySetValue::getProperties()
559 {
560 return writerfilter::Reference<Properties>::Pointer_t
561 (mpPropertySet->clone());
562 }
563
toString() const564 string OOXMLPropertySetValue::toString() const
565 {
566 char sBuffer[256];
567
568 snprintf(sBuffer, sizeof(sBuffer), "t:%p, m:%p", this, mpPropertySet.get());
569
570 return "OOXMLPropertySetValue(" + string(sBuffer) + ")";
571 }
572
clone() const573 OOXMLValue * OOXMLPropertySetValue::clone() const
574 {
575 return new OOXMLPropertySetValue(*this);
576 }
577
578 /*
579 class OOXMLIntegerValue
580 */
581
OOXMLIntegerValue(sal_Int32 nValue)582 OOXMLIntegerValue::OOXMLIntegerValue(sal_Int32 nValue)
583 : mnValue(nValue)
584 {
585 }
586
OOXMLIntegerValue(const rtl::OUString & rValue)587 OOXMLIntegerValue::OOXMLIntegerValue(const rtl::OUString & rValue)
588 : mnValue(0)
589 {
590 mnValue = rValue.toInt32();
591 }
592
~OOXMLIntegerValue()593 OOXMLIntegerValue::~OOXMLIntegerValue()
594 {
595 }
596
getInt() const597 sal_Int32 OOXMLIntegerValue::getInt() const
598 {
599 return mnValue;
600 }
601
getAny() const602 uno::Any OOXMLIntegerValue::getAny() const
603 {
604 uno::Any aResult(mnValue);
605
606 return aResult;
607 }
608
clone() const609 OOXMLValue * OOXMLIntegerValue::clone() const
610 {
611 return new OOXMLIntegerValue(*this);
612 }
613
toString() const614 string OOXMLIntegerValue::toString() const
615 {
616 char buffer[256];
617 snprintf(buffer, sizeof(buffer), "%" SAL_PRIdINT32, mnValue);
618
619 return buffer;
620 }
621
622 /*
623 class OOXMLHexValue
624 */
625
OOXMLHexValue(sal_uInt32 nValue)626 OOXMLHexValue::OOXMLHexValue(sal_uInt32 nValue)
627 : mnValue(nValue)
628 {
629 }
630
OOXMLHexValue(const rtl::OUString & rValue)631 OOXMLHexValue::OOXMLHexValue(const rtl::OUString & rValue)
632 {
633 mnValue = static_cast<sal_uInt32>(rValue.toInt32(16));
634 }
635
~OOXMLHexValue()636 OOXMLHexValue::~OOXMLHexValue()
637 {
638 }
639
getInt() const640 sal_Int32 OOXMLHexValue::getInt() const
641 {
642 return mnValue;
643 }
644
clone() const645 OOXMLValue * OOXMLHexValue::clone() const
646 {
647 return new OOXMLHexValue(*this);
648 }
649
toString() const650 string OOXMLHexValue::toString() const
651 {
652 char buffer[256];
653 snprintf(buffer, sizeof(buffer), "0x%" SAL_PRIxUINT32, mnValue);
654
655 return buffer;
656 }
657
658 /*
659 class OOXMLShapeValue
660 */
661
662
OOXMLShapeValue(uno::Reference<XShape> rShape)663 OOXMLShapeValue::OOXMLShapeValue(uno::Reference<XShape> rShape)
664 : mrShape(rShape)
665 {
666 }
667
~OOXMLShapeValue()668 OOXMLShapeValue::~OOXMLShapeValue()
669 {
670 }
671
getAny() const672 uno::Any OOXMLShapeValue::getAny() const
673 {
674 return uno::Any(mrShape);
675 }
676
toString() const677 string OOXMLShapeValue::toString() const
678 {
679 return "Shape";
680 }
681
clone() const682 OOXMLValue * OOXMLShapeValue::clone() const
683 {
684 return new OOXMLShapeValue(mrShape);
685 }
686
687 /*
688 class OOXMLTableImpl
689 */
690
OOXMLTableImpl()691 OOXMLTableImpl::OOXMLTableImpl()
692 {
693 }
694
~OOXMLTableImpl()695 OOXMLTableImpl::~OOXMLTableImpl()
696 {
697 }
698
resolve(Table & rTable)699 void OOXMLTableImpl::resolve(Table & rTable)
700 {
701 #ifdef DEBUG_PROTOCOL
702 Table::Pointer_t pTable(new TableProtocol(&rTable, debug_logger));
703 #else
704 Table * pTable = &rTable;
705 #endif
706
707 int nPos = 0;
708
709 PropertySets_t::iterator it = mPropertySets.begin();
710 PropertySets_t::iterator itEnd = mPropertySets.end();
711
712 while (it != itEnd)
713 {
714 writerfilter::Reference<Properties>::Pointer_t pProperties
715 ((*it)->getProperties());
716
717 if (pProperties.get() != NULL)
718 pTable->entry(nPos, pProperties);
719
720 ++nPos;
721 it++;
722 }
723 }
724
add(ValuePointer_t pPropertySet)725 void OOXMLTableImpl::add(ValuePointer_t pPropertySet)
726 {
727 if (pPropertySet.get() != NULL)
728 mPropertySets.push_back(pPropertySet);
729 }
730
getType() const731 string OOXMLTableImpl::getType() const
732 {
733 return "OOXMLTableImpl";
734 }
735
clone() const736 OOXMLTable * OOXMLTableImpl::clone() const
737 {
738 return new OOXMLTableImpl(*this);
739 }
740
741 /*
742 class: OOXMLPropertySetEntryToString
743 */
744
OOXMLPropertySetEntryToString(Id nId)745 OOXMLPropertySetEntryToString::OOXMLPropertySetEntryToString(Id nId)
746 : mnId(nId)
747 {
748 }
749
~OOXMLPropertySetEntryToString()750 OOXMLPropertySetEntryToString::~OOXMLPropertySetEntryToString()
751 {
752 }
753
sprm(Sprm &)754 void OOXMLPropertySetEntryToString::sprm(Sprm & /*rSprm*/)
755 {
756 }
757
attribute(Id nId,Value & rValue)758 void OOXMLPropertySetEntryToString::attribute(Id nId, Value & rValue)
759 {
760 if (nId == mnId)
761 mStr = rValue.getString();
762 }
763
getString() const764 const ::rtl::OUString & OOXMLPropertySetEntryToString::getString() const
765 {
766 return mStr;
767 }
768
OOXMLPropertySetEntryToInteger(Id nId)769 OOXMLPropertySetEntryToInteger::OOXMLPropertySetEntryToInteger(Id nId)
770 : mnId(nId)
771 {
772 }
773
~OOXMLPropertySetEntryToInteger()774 OOXMLPropertySetEntryToInteger::~OOXMLPropertySetEntryToInteger()
775 {
776 }
777
sprm(Sprm &)778 void OOXMLPropertySetEntryToInteger::sprm(Sprm & /*rSprm*/)
779 {
780 }
781
attribute(Id nId,Value & rValue)782 void OOXMLPropertySetEntryToInteger::attribute(Id nId, Value & rValue)
783 {
784 if (nId == mnId)
785 mnValue = rValue.getInt();
786 }
787
getValue() const788 int OOXMLPropertySetEntryToInteger::getValue() const
789 {
790 return mnValue;
791 }
792
793 }}
794