00001 /* 00002 * Copyright 1999-2004 The Apache Software Foundation. 00003 * 00004 * Licensed under the Apache License, Version 2.0 (the "License"); 00005 * you may not use this file except in compliance with the License. 00006 * You may obtain a copy of the License at 00007 * 00008 * http://www.apache.org/licenses/LICENSE-2.0 00009 * 00010 * Unless required by applicable law or agreed to in writing, software 00011 * distributed under the License is distributed on an "AS IS" BASIS, 00012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00013 * See the License for the specific language governing permissions and 00014 * limitations under the License. 00015 */ 00016 #if !defined(FORMATTERTOXML_UNICODE_HEADER_GUARD_1357924680) 00017 #define FORMATTERTOXML_UNICODE_HEADER_GUARD_1357924680 00018 00019 00020 // Base include file. Must be first. 00021 #include "xalanc/XMLSupport/XMLSupportDefinitions.hpp" 00022 00023 00024 00025 #include "xalanc/XMLSupport/XalanXMLSerializerBase.hpp" 00026 00027 00028 00029 #include "xercesc/sax/AttributeList.hpp" 00030 00031 00032 00033 #include "xalanc/PlatformSupport/DoubleSupport.hpp" 00034 #include "xalanc/PlatformSupport/XalanOutputStream.hpp" 00035 #include "xalanc/PlatformSupport/XalanUnicode.hpp" 00036 00037 00038 00039 #include <xalanc/DOMSupport/DOMServices.hpp> 00040 00041 00042 00043 XALAN_CPP_NAMESPACE_BEGIN 00044 00045 00049 template< 00050 class UnicodeWriter, 00051 class ConstantsType, 00052 class CharPredicate, 00053 class IndentHandler, 00054 FormatterListener::eXMLVersion XMLVersion> 00055 class XALAN_XMLSUPPORT_EXPORT FormatterToXMLUnicode : public XalanXMLSerializerBase 00056 { 00057 public: 00058 00059 typedef typename UnicodeWriter::value_type value_type; 00060 00061 enum 00062 { 00063 eDefaultIndentAmount = 0 00064 }; 00065 00082 FormatterToXMLUnicode( 00083 MemoryManager& theManager, 00084 Writer& writer, 00085 const XalanDOMString& encoding, 00086 const XalanDOMString& doctypeSystem = s_emptyString, 00087 const XalanDOMString& doctypePublic = s_emptyString, 00088 bool xmlDecl = true, 00089 const XalanDOMString& standalone = s_emptyString, 00090 size_type indent = eDefaultIndentAmount) : 00091 XalanXMLSerializerBase( 00092 theManager, 00093 XMLVersion, 00094 encoding, 00095 doctypeSystem, 00096 doctypePublic, 00097 xmlDecl, 00098 standalone), 00099 m_stringBuffer(theManager), 00100 m_writer(writer, theManager), 00101 m_constants(), 00102 m_charPredicate(), 00103 m_indentHandler(m_writer , indent) 00104 { 00105 } 00106 00107 static FormatterToXMLUnicode* 00108 create( 00109 MemoryManagerType& theManager, 00110 Writer& writer, 00111 const XalanDOMString& encoding, 00112 const XalanDOMString& doctypeSystem = s_emptyString, 00113 const XalanDOMString& doctypePublic = s_emptyString, 00114 bool xmlDecl = true, 00115 const XalanDOMString& standalone = s_emptyString, 00116 size_type indent = eDefaultIndentAmount) 00117 { 00118 00119 typedef FormatterToXMLUnicode ThisType; 00120 00121 XalanMemMgrAutoPtr<ThisType, false> theGuard( theManager , (ThisType*)theManager.allocate(sizeof(ThisType))); 00122 00123 ThisType* theResult = theGuard.get(); 00124 00125 new (theResult) ThisType( 00126 theManager, 00127 writer, 00128 encoding, 00129 doctypeSystem, 00130 doctypePublic, 00131 xmlDecl, 00132 standalone, 00133 indent); 00134 00135 theGuard.release(); 00136 00137 return theResult; 00138 } 00139 00140 virtual 00141 ~FormatterToXMLUnicode() 00142 { 00143 } 00144 00145 Writer* 00146 getWriter() const 00147 { 00148 return m_writer.getWriter(); 00149 } 00150 00151 // These are inherited from XalanXMLSerializerBase... 00152 00153 virtual void 00154 endDocument() 00155 { 00156 m_indentHandler.setStartNewLine(true); 00157 00158 m_indentHandler.indent(); 00159 00160 flushBuffer(); 00161 } 00162 00163 virtual void 00164 startElement( 00165 const XMLCh* const name, 00166 AttributeList& attrs) 00167 { 00168 generateDoctypeDecl(name); 00169 00170 writeParentTagEnd(); 00171 00172 m_indentHandler.setPreserve(false); 00173 00174 m_indentHandler.indent(); 00175 00176 m_indentHandler.setStartNewLine(true); 00177 00178 m_writer.write(value_type(XalanUnicode::charLessThanSign)); 00179 00180 writeName(name); 00181 00182 const unsigned int nAttrs = attrs.getLength(); 00183 00184 for (unsigned int i = 0; i < nAttrs ; i++) 00185 { 00186 processAttribute(attrs.getName(i), attrs.getValue(i)); 00187 } 00188 00189 // Flag the current element as not yet having any children. 00190 openElementForChildren(); 00191 00192 m_indentHandler.increaseIndent(); 00193 00194 m_indentHandler.setPrevText(false); 00195 } 00196 00197 virtual void 00198 endElement(const XMLCh* const name) 00199 { 00200 m_indentHandler.decreaseIndent(); 00201 00202 const bool hasChildNodes = childNodesWereAdded(); 00203 00204 if (hasChildNodes == true) 00205 { 00206 m_indentHandler.indent(); 00207 00208 m_writer.write(value_type(XalanUnicode::charLessThanSign)); 00209 m_writer.write(value_type(XalanUnicode::charSolidus)); 00210 00211 writeName(name); 00212 } 00213 else 00214 { 00215 if(m_spaceBeforeClose == true) 00216 { 00217 m_writer.write(value_type(XalanUnicode::charSpace)); 00218 } 00219 00220 m_writer.write(value_type(XalanUnicode::charSolidus)); 00221 } 00222 00223 m_writer.write(value_type(XalanUnicode::charGreaterThanSign)); 00224 00225 if (hasChildNodes == true) 00226 { 00227 m_indentHandler.pop_preserve(); 00228 } 00229 00230 m_indentHandler.setPrevText(false); 00231 } 00232 00233 virtual void 00234 charactersRaw( 00235 const XMLCh* const chars, 00236 const unsigned int length) 00237 { 00238 writeParentTagEnd(); 00239 00240 m_indentHandler.setPreserve(true); 00241 00242 m_writer.write(chars, length); 00243 } 00244 00245 00246 virtual void 00247 entityReference(const XMLCh* const name) 00248 { 00249 writeParentTagEnd(); 00250 00251 m_indentHandler.indent(); 00252 00253 m_writer.write(value_type(XalanUnicode::charAmpersand)); 00254 00255 writeName(name); 00256 00257 m_writer.write(value_type(XalanUnicode::charSemicolon)); 00258 } 00259 00260 virtual void 00261 comment(const XMLCh* const data) 00262 { 00263 writeParentTagEnd(); 00264 00265 m_indentHandler.indent(); 00266 00267 m_writer.write(value_type(XalanUnicode::charLessThanSign)); 00268 m_writer.write(value_type(XalanUnicode::charExclamationMark)); 00269 m_writer.write(value_type(XalanUnicode::charHyphenMinus)); 00270 m_writer.write(value_type(XalanUnicode::charHyphenMinus)); 00271 00272 writeNormalizedData(data, XalanDOMString::length(data)); 00273 00274 m_writer.write(value_type(XalanUnicode::charHyphenMinus)); 00275 m_writer.write(value_type(XalanUnicode::charHyphenMinus)); 00276 m_writer.write(value_type(XalanUnicode::charGreaterThanSign)); 00277 00278 m_indentHandler.setStartNewLine(true); 00279 } 00280 00281 virtual const XalanDOMString& 00282 getEncoding() const 00283 { 00284 return m_constants.s_encodingString; 00285 } 00286 00287 protected: 00288 00289 virtual void 00290 flushBuffer() 00291 { 00292 m_writer.flushBuffer(); 00293 } 00294 00295 void 00296 writeXMLHeader() 00297 { 00298 // "<?xml version=\"" 00299 m_writer.write( 00300 m_constants.s_xmlHeaderStartString, 00301 m_constants.s_xmlHeaderStartStringLength); 00302 00303 if (length(m_version) != 0) 00304 { 00305 m_writer.write(m_version); 00306 } 00307 else 00308 { 00309 m_writer.write( 00310 m_constants.s_defaultVersionString, 00311 m_constants.s_defaultVersionStringLength); 00312 } 00313 00314 // "\" encoding=\"" 00315 m_writer.write( 00316 m_constants.s_xmlHeaderEncodingString, 00317 m_constants.s_xmlHeaderEncodingStringLength); 00318 00319 m_writer.write(m_encoding); 00320 00321 if (length(m_standalone) != 0) 00322 { 00323 m_writer.write( 00324 m_constants.s_xmlHeaderStandaloneString, 00325 m_constants.s_xmlHeaderStandaloneStringLength); 00326 00327 m_writer.write(m_standalone); 00328 } 00329 00330 m_writer.write( 00331 m_constants.s_xmlHeaderEndString, 00332 m_constants.s_xmlHeaderEndStringLength); 00333 00334 if (getNeedToOutputDoctypeDecl() == false) 00335 { 00336 m_indentHandler.outputLineSep(); 00337 } 00338 } 00339 00340 00341 void 00342 writeDoctypeDecl(const XalanDOMChar* name) 00343 { 00344 // "<!DOCTYPE " 00345 m_writer.write( 00346 m_constants.s_doctypeHeaderStartString, 00347 m_constants.s_doctypeHeaderStartStringLength); 00348 00349 m_writer.write(name); 00350 00351 if(length(m_doctypePublic) != 0) 00352 { 00353 // " PUBLIC \"" 00354 m_writer.write( 00355 m_constants.s_doctypeHeaderPublicString, 00356 m_constants.s_doctypeHeaderPublicStringLength); 00357 00358 writeName(m_doctypePublic.c_str()); 00359 00360 m_writer.write(value_type(XalanUnicode::charQuoteMark)); 00361 m_writer.write(value_type(XalanUnicode::charSpace)); 00362 m_writer.write(value_type(XalanUnicode::charQuoteMark)); 00363 } 00364 else 00365 { 00366 // " SYSTEM \"" 00367 m_writer.write( 00368 m_constants.s_doctypeHeaderSystemString, 00369 m_constants.s_doctypeHeaderSystemStringLength); 00370 } 00371 00372 writeName(m_doctypeSystem.c_str()); 00373 00374 m_writer.write(value_type(XalanUnicode::charQuoteMark)); 00375 m_writer.write(value_type(XalanUnicode::charGreaterThanSign)); 00376 00377 outputNewline(); 00378 } 00379 00380 00381 void 00382 writeProcessingInstruction( 00383 const XMLCh* target, 00384 const XMLCh* data) 00385 { 00386 writeParentTagEnd(); 00387 00388 m_indentHandler.indent(); 00389 00390 m_writer.write(value_type(XalanUnicode::charLessThanSign)); 00391 m_writer.write(value_type(XalanUnicode::charQuestionMark)); 00392 writeName(target); 00393 00394 const XalanDOMString::size_type len = length(data); 00395 00396 // We need to make sure there is a least one whitespace character 00397 // between the target and the data. 00398 if ( len > 0 && !isXMLWhitespace(data[0])) 00399 { 00400 m_writer.write(value_type(XalanUnicode::charSpace)); 00401 } 00402 00403 writeNormalizedData(data, len); 00404 00405 m_writer.write(value_type(XalanUnicode::charQuestionMark)); 00406 m_writer.write(value_type(XalanUnicode::charGreaterThanSign)); 00407 00408 // If outside of an element, then put in a new line. This whitespace 00409 // is not significant. 00410 if (outsideDocumentElement() == true) 00411 { 00412 outputNewline(); 00413 } 00414 } 00415 00416 void 00417 writeCharacters( 00418 const XMLCh* chars, 00419 unsigned int length) 00420 { 00421 assert(length != 0); 00422 00423 writeParentTagEnd(); 00424 00425 m_indentHandler.setPreserve(true); 00426 00427 unsigned int i = 0; 00428 unsigned int firstIndex = 0; 00429 00430 while(i < length) 00431 { 00432 const XalanDOMChar ch = chars[i]; 00433 00434 if(m_charPredicate.range(ch) == true) 00435 { 00436 safeWriteContent(chars + firstIndex, i - firstIndex); 00437 00438 i = writeNormalizedCharBig(chars, i, length); 00439 00440 ++i; 00441 00442 firstIndex = i; 00443 } 00444 else if(m_charPredicate.content(ch) == false) 00445 { 00446 ++i; 00447 } 00448 else 00449 { 00450 safeWriteContent(chars + firstIndex, i - firstIndex); 00451 00452 writeDefaultEscape(ch); 00453 00454 ++i; 00455 00456 firstIndex = i; 00457 } 00458 } 00459 00460 safeWriteContent(chars + firstIndex, i - firstIndex); 00461 00462 m_indentHandler.setPrevText(true); 00463 } 00464 00465 00466 void 00467 writeCDATA( 00468 const XMLCh* chars, 00469 unsigned int length) 00470 { 00471 assert(length != 0); 00472 00473 writeParentTagEnd(); 00474 00475 m_indentHandler.setPreserve(true); 00476 00477 m_indentHandler.indent(); 00478 00479 m_writer.write( 00480 m_constants.s_cdataOpenString, 00481 m_constants.s_cdataOpenStringLength); 00482 00483 bool outsideCDATA = false; 00484 00485 writeCDATAChars(chars, length, outsideCDATA); 00486 00487 if (outsideCDATA == false) 00488 { 00489 m_writer.write( 00490 m_constants.s_cdataCloseString, 00491 m_constants.s_cdataCloseStringLength); 00492 } 00493 } 00494 00498 void 00499 outputNewline() 00500 { 00501 m_writer.outputNewline(); 00502 } 00503 00507 void 00508 writeDefaultEscape(XalanDOMChar ch) 00509 { 00510 assert(m_charPredicate.content(ch) == true); 00511 00512 if(!writeDefaultEntity(ch)) 00513 { 00514 if (XalanUnicode::charLF == ch) 00515 { 00516 outputNewline(); 00517 } 00518 else 00519 { 00520 if(m_charPredicate.isForbidden(ch) == true) 00521 { 00522 throwInvalidXMLCharacterException( 00523 ch, 00524 m_version, 00525 getMemoryManager()); 00526 } 00527 else 00528 { 00529 writeNumericCharacterReference(ch); 00530 } 00531 } 00532 } 00533 } 00534 00538 void 00539 writeDefaultAttributeEscape(XalanDOMChar ch) 00540 { 00541 assert(m_charPredicate.attribute(ch) == true); 00542 00543 if(writeDefaultAttributeEntity(ch) == false) 00544 { 00545 if(m_charPredicate.isForbidden(ch) == true) 00546 { 00547 throwInvalidXMLCharacterException( 00548 ch, 00549 m_version, 00550 getMemoryManager()); 00551 } 00552 else 00553 { 00554 writeNumericCharacterReference(ch); 00555 } 00556 00557 } 00558 } 00559 00564 bool 00565 writeDefaultEntity(XalanDOMChar ch) 00566 { 00567 if (XalanUnicode::charLessThanSign == ch) 00568 { 00569 m_writer.write( 00570 m_constants.s_lessThanEntityString, 00571 m_constants.s_lessThanEntityStringLength); 00572 } 00573 else if (XalanUnicode::charGreaterThanSign == ch) 00574 { 00575 m_writer.write( 00576 m_constants.s_greaterThanEntityString, 00577 m_constants.s_greaterThanEntityStringLength); 00578 } 00579 else if (XalanUnicode::charAmpersand == ch) 00580 { 00581 m_writer.write( 00582 m_constants.s_ampersandEntityString, 00583 m_constants.s_ampersandEntityStringLength); 00584 } 00585 else 00586 { 00587 return false; 00588 } 00589 00590 return true; 00591 } 00592 00597 bool 00598 writeDefaultAttributeEntity(XalanDOMChar ch) 00599 { 00600 if (writeDefaultEntity(ch) == true) 00601 { 00602 return true; 00603 } 00604 else if (XalanUnicode::charQuoteMark == ch) 00605 { 00606 m_writer.write( 00607 m_constants.s_quoteEntityString, 00608 m_constants.s_quoteEntityStringLength); 00609 } 00610 else 00611 { 00612 return false; 00613 } 00614 00615 return true; 00616 } 00617 00622 void 00623 writeParentTagEnd() 00624 { 00625 if(markParentForChildren() == true) 00626 { 00627 m_writer.write(value_type(XalanUnicode::charGreaterThanSign)); 00628 00629 m_indentHandler.setPrevText(false); 00630 00631 m_indentHandler.push_preserve(); 00632 } 00633 } 00634 00641 XalanDOMString::size_type 00642 writeNormalizedChar( 00643 XalanDOMChar ch, 00644 const XalanDOMChar chars[], 00645 XalanDOMString::size_type start, 00646 XalanDOMString::size_type length) 00647 { 00648 if (XalanUnicode::charLF == ch) 00649 { 00650 outputNewline(); 00651 } 00652 else 00653 { 00654 if(m_charPredicate.isCharRefForbidden(ch)) 00655 { 00656 throwInvalidXMLCharacterException( 00657 ch, 00658 m_version, 00659 getMemoryManager()); 00660 } 00661 else 00662 { 00663 start = m_writer.write( chars, start, length); 00664 } 00665 } 00666 00667 return start; 00668 } 00669 00670 void 00671 writeNumericCharacterReference(unsigned long theNumber) 00672 { 00673 m_writer.write(value_type(XalanUnicode::charAmpersand)); 00674 m_writer.write(value_type(XalanUnicode::charNumberSign)); 00675 00676 m_writer.write(UnsignedLongToDOMString(theNumber, m_stringBuffer)); 00677 clear(m_stringBuffer); 00678 00679 m_writer.write(value_type(XalanUnicode::charSemicolon)); 00680 } 00681 00682 XalanDOMString::size_type 00683 writeNormalizedCharBig( 00684 const XalanDOMChar chars[], 00685 XalanDOMString::size_type start, 00686 XalanDOMString::size_type length) 00687 { 00688 assert( start < length); 00689 00690 const XalanDOMChar ch = chars[start]; 00691 00692 assert(m_charPredicate.range(ch) == true); 00693 00694 if (XMLVersion == XML_VERSION_1_1 && 00695 XalanUnicode::charLSEP == ch) 00696 { 00697 writeNumericCharacterReference(ch); 00698 } 00699 else 00700 { 00701 start = m_writer.write(chars, start, length); 00702 } 00703 00704 return start; 00705 } 00706 00713 void 00714 writeCDATAChars( 00715 const XalanDOMChar chars[], 00716 XalanDOMString::size_type length, 00717 bool& outsideCDATA) 00718 { 00719 XalanDOMString::size_type i = 0; 00720 00721 while(i < length) 00722 { 00723 // If "]]>", which would close the CDATA appears in 00724 // the content, we have to put the first two characters 00725 // in the CDATA section, close the CDATA section, then 00726 // open a new one and add the last character. 00727 00728 const XalanDOMChar theChar = chars[i]; 00729 00730 if (theChar == XalanUnicode::charRightSquareBracket && 00731 i - length > 2 && 00732 XalanUnicode::charRightSquareBracket == chars[i + 1] && 00733 XalanUnicode::charGreaterThanSign == chars[i + 2]) 00734 { 00735 if (outsideCDATA == true) 00736 { 00737 m_writer.write( 00738 m_constants.s_cdataCloseString, 00739 m_constants.s_cdataCloseStringLength); 00740 } 00741 00742 m_writer.write(value_type(XalanUnicode::charRightSquareBracket)); 00743 m_writer.write(value_type(XalanUnicode::charRightSquareBracket)); 00744 00745 m_writer.write( 00746 m_constants.s_cdataCloseString, 00747 m_constants.s_cdataCloseStringLength); 00748 00749 m_writer.write( 00750 m_constants.s_cdataOpenString, 00751 m_constants.s_cdataOpenStringLength); 00752 00753 m_writer.write(value_type(XalanUnicode::charGreaterThanSign)); 00754 00755 outsideCDATA = false; 00756 00757 i += 2; 00758 } 00759 else 00760 { 00761 if (XalanUnicode::charLF == theChar) 00762 { 00763 outputNewline(); 00764 } 00765 else if(m_charPredicate.isCharRefForbidden(theChar)) 00766 { 00767 throwInvalidXMLCharacterException( 00768 theChar, 00769 m_version, 00770 getMemoryManager()); 00771 } 00772 else 00773 { 00774 i = m_writer.writeCDATAChar(chars, i, length, outsideCDATA); 00775 } 00776 } 00777 00778 ++i; 00779 } 00780 00781 if(outsideCDATA == true) 00782 { 00783 m_writer.write( 00784 m_constants.s_cdataOpenString, 00785 m_constants.s_cdataOpenStringLength); 00786 } 00787 } 00788 00789 00796 void 00797 writeAttrString( 00798 const XalanDOMChar* theString, 00799 XalanDOMString::size_type theStringLength) 00800 { 00801 assert(theString != 0); 00802 00803 XalanDOMString::size_type i = 0; 00804 XalanDOMString::size_type firstIndex = 0; 00805 00806 while(i < theStringLength) 00807 { 00808 const XalanDOMChar ch = theString[i]; 00809 00810 if(m_charPredicate.range(ch) == true) 00811 { 00812 safeWriteContent(theString + firstIndex, i - firstIndex); 00813 00814 i = writeNormalizedCharBig(theString, i, theStringLength); 00815 00816 ++i; 00817 00818 firstIndex = i; 00819 } 00820 else if (m_charPredicate.attribute(ch) == false) 00821 { 00822 ++i; 00823 } 00824 else 00825 { 00826 safeWriteContent(theString + firstIndex, i - firstIndex); 00827 00828 writeDefaultAttributeEscape(ch); 00829 00830 ++i; 00831 00832 firstIndex = i; 00833 } 00834 } 00835 00836 safeWriteContent(theString + firstIndex, i - firstIndex); 00837 } 00838 00839 private: 00840 00846 void 00847 processAttribute( 00848 const XalanDOMChar* name, 00849 const XalanDOMChar* value) 00850 { 00851 m_writer.write(value_type(XalanUnicode::charSpace)); 00852 writeName(name); 00853 m_writer.write(value_type(XalanUnicode::charEqualsSign)); 00854 m_writer.write(value_type(XalanUnicode::charQuoteMark)); 00855 writeAttrString(value, length(value)); 00856 m_writer.write(value_type(XalanUnicode::charQuoteMark)); 00857 } 00858 00864 void 00865 writeNormalizedData( 00866 const XalanDOMChar* theData, 00867 XalanDOMString::size_type theLength) 00868 { 00869 for (XalanDOMString::size_type i = 0; i < theLength; ++i) 00870 { 00871 const XalanDOMChar theChar = theData[i]; 00872 00873 i = writeNormalizedChar(theChar, theData, i, theLength); 00874 } 00875 } 00876 00877 void 00878 safeWriteContent( 00879 const XalanDOMChar* theChars, 00880 XalanDOMString::size_type theLength) 00881 { 00882 for(size_type i = 0; i < theLength; ++i) 00883 { 00884 m_writer.write(value_type(theChars[i])); 00885 } 00886 } 00887 00888 void 00889 writeName(const XalanDOMChar* theChars) 00890 { 00891 assert( theChars != 0); 00892 00893 m_writer.writeNameChar(theChars, length(theChars)); 00894 } 00895 00896 private: 00897 00898 // These are not implemented. 00899 FormatterToXMLUnicode(const FormatterToXMLUnicode&); 00900 00901 FormatterToXMLUnicode& 00902 operator=(const FormatterToXMLUnicode&); 00903 00904 bool 00905 operator==(const FormatterToXMLUnicode&) const; 00906 00907 00908 // Data members... 00909 XalanDOMString m_stringBuffer; 00910 00911 UnicodeWriter m_writer; 00912 00913 ConstantsType m_constants; 00914 00915 CharPredicate m_charPredicate; 00916 00917 IndentHandler m_indentHandler; 00918 }; 00919 00920 00921 00922 XALAN_CPP_NAMESPACE_END 00923 00924 00925 00926 #endif // FORMATTERTOXML_UNICODE_HEADER_GUARD_1357924680
Doxygen and GraphViz are used to generate this API documentation from the Xalan-C header files.
Xalan-C++ XSLT Processor Version 1.10 |
|