³ò Hí“Lc @sŽdZddkZddkZddkTddkTddkTddkZddkTdZdZ dZ e Z e d„Zdfd „ƒYZd fd „ƒYZd efd „ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZd efd!„ƒYZd"efd#„ƒYZd$efd%„ƒYZd&efd'„ƒYZd(efd)„ƒYZd*efd+„ƒYZ d,efd-„ƒYZ!d.efd/„ƒYZ"d0efd1„ƒYZ#dfd2„ƒYZ$d3fd4„ƒYZ%d5fd6„ƒYZ&d7fd8„ƒYZ'dS(9s³template.py: Template-related objects for PyStyler $Revision: 1.40 $ $Date: 2010/09/17 22:35:52 $ Exports: class TmplPool: Pool of previously parsed templates (singleton) class TToken: Abstract class for tokens within a template Classes derived from TToken include: LiteralToken NextURLToken AuthorToken PrevTitleToken BodyToken PrevURLToken ElseToken SeeToken EndifToken TitleToken IfNextToken UpdateToken IfPrevToken UpTitleToken IfSeeToken UpURLToken IfUpToken URLToken NextTitleToken Note: this class and all those derived from it must appear before the Template class because Template.formatScanners refers to these class constructors. class Template: Represents one template file class OpenCond: Tracks conditionals during file parsing class IfStack: Tracks conditionals during expansion class Condition: Tracks one conditional during expansion iÿÿÿÿN(t*tTemplatet%t;cCs$tipt|ƒt_ntiS(s$Singleton wrapper for _TmplPool (t _TmplPooltinstance(t defTemplate((s%/u/john/projects/pystyler/template.pytTmplPool7s RcBs)eZdZdZdd„Zd„ZRS(sêRepresents a pool of previously parsed template files. Exports: _TmplPool(defTemplate): [ if (defTemplate is a string or None) -> return a new, empty TmplPool object with defTemplate as its default template name, defaulting to DEFAULT_TEMPLATE ] .__getitem__(self, name): [ if (name is a string or None) -> if self contains a Template for file eff-name(name) -> return that Template else if eff_name(name) names a readable, valid template file -> self := self + a Template object representing that file return that Template object else -> Log() +:= error message(s) raise IOError ] State/Invariants: .__templateMap: [ a dictionary mapping K_i |-> V_i such that for every template V_i contained in self, K_i is that template's file name ] cCs0h|_|djo t|_n ||_dS(s*Constructor for a TmplPool object N(t_TmplPool__templateMaptNonetDEFAULT_TEMPLATER(tselfR((s%/u/john/projects/pystyler/template.pyt__init__]s   cCsb|djo |i}n|}y|i|SWntj onXt|ƒ}||i|<|S(sLGiven a file name, retrieves it as a Template, parsing if necessary N(R RRtKeyErrorR(R tkeytfileNametresult((s%/u/john/projects/pystyler/template.pyt __getitem__is  N(t__name__t __module__t__doc__R RR R(((s%/u/john/projects/pystyler/template.pyR?s tTTokencBs)eZdZd„Zd„Zd„ZRS(sÔ Abstract class for all template atoms (tokens) There are two kinds of classes that inherit from TToken: - Each format code (e.g., "%ifnext;") has its own class. - Text other than format codes is represented by a LiteralToken In each class, the constructor is called during scanning of the template file, and checks validity of the construct (e.g., that an "%else;" matches an open "%if...;"), and the .expand() method is called during expansion of a body file to generate the actual HTML. Exports: TToken ( openCondStack, scan ): [ if (openCondStack is a stack of OpenCond objects representing %if-constructs not yet matched by "%endif;") and (scan is a Scan object) -> if this token is valid in the context of openCondStack -> openCondStack := openCondStack adjusted for the token, with a new OpenCond pushed containing scan.lineNo if the token is an %if-construct else -> raise SyntaxError ] .writeLink ( outFile, fromTopic, toTarget ): [ if (outFile is a writeable file handle) and (fromTopic is a Topic object) and (toTarget is a Target object) -> outFile +:= an HTML link that will point from fromTopic's .html file to the target represented by toTarget ] .writeStdLink ( outFile, fromTopic, toTarget ): [ if (outFile is a writeable file handle) and (fromTopic is a Topic object) and (toTarget is a Target object) -> outFile +:= an HTML ... construct whose tag will point from fromTopic's .html file to the target represented by toTarget and whose link text is the title of toTarget's topic ] Virtual methods: .expand ( ifStack, body, outFile ): [ if (ifStack is an IfStack object representing the current nesting of %if-constructs) and (body is a Body object representing the context in which the token is to be expanded) and (outFile is a writeable file handle) -> if self is an invalid conditional expansion token in the context of ifStack -> raise SyntaxError else if self is a valid conditional expansion token in the context of ifStack -> ifStack := ifStack adjusted to account for processing self else if ifStack is skipping -> I else -> outFile +:= the expansion of self in the context of body ] cCsdS(N((R t openCondStacktscan((s%/u/john/projects/pystyler/template.pyR ÄscCs|id|i|ƒƒdS(sECreate an link from `fromTopic' to `toTarget' s N(twritetrelPath(R toutFilet fromTopicttoTarget((s%/u/john/projects/pystyler/template.pyt writeLinkÊs cCs.|i|||ƒ|id|iiƒdS(sGWrite an ... construct linking to `toTarget' s%sN(RRttopicttitle(R RRR((s%/u/john/projects/pystyler/template.pyt writeStdLinkÓs(RRRR RR (((s%/u/john/projects/pystyler/template.pyR‡s9  t LiteralTokencBs eZdZd„Zd„ZRS(sqRepresents text from a template containing no format codes. Exports: LiteralToken ( openCondStack, scan, text ): [ if (openCondStack and scan are as in the base class) and (text is a string) -> return a new LiteralToken representing text ] State/Invariants: .text: [ as passed to the constructor ] cCs ti|||ƒ||_dS(s'Constructor for a LiteralToken N(RR ttext(R RRR"((s%/u/john/projects/pystyler/template.pyR ñscCs%|iƒp|i|iƒndS(s>Expand a literal token by adding self.text to outFile N(t isSkippingRR"(R tifStacktbodyR((s%/u/john/projects/pystyler/template.pytexpandús (RRRR R&(((s%/u/john/projects/pystyler/template.pyR!âs  t AuthorTokencBseZdZd„ZRS(s)Represents an "%author;" format code cCs0|iƒ o|io|i|iƒndS(s*Expand the "%author;" format code N(R#tauthorR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&s (RRRR&(((s%/u/john/projects/pystyler/template.pyR'st BodyTokencBseZdZd„ZRS(sIRepresents a "%body;" format code, which expands to the body content cCs%|iƒp|i|iƒndS(s(Expand the "%body;" format code N(R#t writelinesR"(R R$R%R((s%/u/john/projects/pystyler/template.pyR&s (RRRR&(((s%/u/john/projects/pystyler/template.pyR)st ElseTokencBs eZdZd„Zd„ZRS(s'Represents an "%else;" format code cCsKti|||ƒt|ƒdjo td‚n|d}|iƒdS(s1Constructor for the "%else;" format code is This `%else;' has no matching ifiÿÿÿÿN(RR tlent SyntaxErrort elseCheck(R RRttopCond((s%/u/john/projects/pystyler/template.pyR s  cCs|iƒdS(s#Expand the `%endif;' token N(R.(R R$R%R((s%/u/john/projects/pystyler/template.pyR&;s (RRRR R&(((s%/u/john/projects/pystyler/template.pyR+s t EndifTokencBs eZdZd„Zd„ZRS(s"Represents an "%endif;" token cCs>ti|||ƒt|ƒdjo td‚n|d=dS(s2Constructor for the "%endif;" format code is(This `%endif;' has no matching `%if...;'iÿÿÿÿN(RR R,R-(R RR((s%/u/john/projects/pystyler/template.pyR Rs cCs|iƒdS(s#Expand the `%endif;' token N(tend(R R$R%R((s%/u/john/projects/pystyler/template.pyR&fs(RRRR R&(((s%/u/john/projects/pystyler/template.pyR0Ks t IfNextTokencBs eZdZd„Zd„ZRS(s$Represents the `%ifnext;' token cCs6ti|||ƒt|idƒ}|i|ƒdS(s-Constructor for the `%ifnext;' token tifnextN(RR tOpenCondtlineNotappend(R RRtnewCond((s%/u/john/projects/pystyler/template.pyR tscCs7|idjo d}nd}|it|ƒƒdS(s$Expand the `%ifnext;' token iiN(tenextR tstartt Condition(R R$R%Rtskipping((s%/u/john/projects/pystyler/template.pyR&…s (RRRR R&(((s%/u/john/projects/pystyler/template.pyR2ns t IfPrevTokencBs eZdZd„Zd„ZRS(s$Represents the `%ifprev;' token cCs6ti|||ƒt|idƒ}|i|ƒdS(s-Constructor for the `%ifprev;' token tifprevN(RR R4R5R6(R RRR7((s%/u/john/projects/pystyler/template.pyR ŸscCs7|idjo d}nd}|it|ƒƒdS(s$Expand the `%ifprev;' token iiN(teprevR R9R:(R R$R%RR;((s%/u/john/projects/pystyler/template.pyR&°s (RRRR R&(((s%/u/john/projects/pystyler/template.pyR<™s t IfSeeTokencBs eZdZd„Zd„ZRS(s#Represents the `%ifsee;' token cCs6ti|||ƒt|idƒ}|i|ƒdS(s,Constructor for the `%ifsee;' token tifseeN(RR R4R5R6(R RRR7((s%/u/john/projects/pystyler/template.pyR ËscCs=t|iƒdjo d}nd}|it|ƒƒdS(s#Expand the `%ifsee;' token iiN(R,tseeListR9R:(R R$R%RR;((s%/u/john/projects/pystyler/template.pyR&Üs (RRRR R&(((s%/u/john/projects/pystyler/template.pyR?Ås t IfUpTokencBs eZdZd„Zd„ZRS(s"Represents the `%ifup;' token cCs6ti|||ƒt|idƒ}|i|ƒdS(s+Constructor for the `%ifup;' token tifupN(RR R4R5R6(R RRR7((s%/u/john/projects/pystyler/template.pyR öscCs4|iio d}nd}|it|ƒƒdS(s"Expand the `%ifup;' token iiN(RtparentR9R:(R R$R%RR;((s%/u/john/projects/pystyler/template.pyR&s (RRRR R&(((s%/u/john/projects/pystyler/template.pyRBðs tNextTitleTokencBseZdZd„ZRS(s'Represents the `%nexttitle;' token cCs6|iƒ o$|io|i|iiiƒndS(s(Expands the `%nexttitle;' token N(R#R8RRR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&"s (RRRR&(((s%/u/john/projects/pystyler/template.pyREst NextURLTokencBseZdZd„ZRS(s%Represents the `%nexturl;' token cCs9|iƒ o'|io|i||i|iƒndS(s&Expands the `%nexturl;' token N(R#R8RR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&6s (RRRR&(((s%/u/john/projects/pystyler/template.pyRF/stPrevTitleTokencBseZdZd„ZRS(s'Represents the `%prevtitle;' token cCs6|iƒ o$|io|i|iiiƒndS(s(Expands the `%prevtitle;' token N(R#R>RRR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&Ks (RRRR&(((s%/u/john/projects/pystyler/template.pyRGDst PrevURLTokencBseZdZd„ZRS(s%Represents the `%prevurl;' token cCs9|iƒ o'|io|i||i|iƒndS(s&Expands the `%prevurl;' token N(R#R>RR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&_s (RRRR&(((s%/u/john/projects/pystyler/template.pyRHXstSeeTokencBseZdZd„ZRS(s!Represents the `%see;' token cCs\|iƒodSnd}x:|iD]/}|i|ƒd}|i||i|ƒq%WdS(s!Expand the `%see;' token Nts; (R#RARR R(R R$R%Rtprefixttarget((s%/u/john/projects/pystyler/template.pyR&ts   (RRRR&(((s%/u/john/projects/pystyler/template.pyRImst TitleTokencBseZdZd„ZRS(s!Represents a `%title;' token cCs(|iƒp|i|iiƒndS(s$Expands the `%title;' token N(R#RRR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&s (RRRR&(((s%/u/john/projects/pystyler/template.pyRM—st UpdateTokencBseZdZd„ZRS(s$Represents the `%update;' token cCs0|iƒ o|io|i|iƒndS(s%Expands the `%update;' token N(R#tupdatedR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&¯s (RRRR&(((s%/u/john/projects/pystyler/template.pyRN©st UpTitleTokencBseZdZd„ZRS(s%Represents the `%uptitle;' token cCs9|iƒ o'|iio|i|iiiƒndS(s&Expands the `%uptitle;' token N(R#RRDRR(R R$R%R((s%/u/john/projects/pystyler/template.pyR&Ãs (RRRR&(((s%/u/john/projects/pystyler/template.pyRP¼st UpURLTokencBseZdZd„ZRS(s#Represents the `%upurl;' token cCsH|iƒ o6|iio)|i||it|iidƒƒndS(s$Expands the `%upurl;' token N(R#RRDRtTargetR (R R$R%R((s%/u/john/projects/pystyler/template.pyR&×s (RRRR&(((s%/u/john/projects/pystyler/template.pyRQÐstURLTokencBseZdZd„ZRS(s!Represents the `%url;' token cCs>|iƒp-|id|iiii|iifƒndS(s"Expands the `%url;' token s %s%s.htmlN(R#RRtplantpathMaptrootURLtpath(R R$R%R((s%/u/john/projects/pystyler/template.pyR&ís  (RRRR&(((s%/u/john/projects/pystyler/template.pyRSæscBsøeZdZdd„Zd„Zd„Zd„Zd„Zhe d<e d<e d<e d <e d <ed <ed <ed <ed<ed<ed<ed<ed<ed<ed<ed<ed<ed if fileName names a readable, valid template file, defaulting to DEFAULT_TEMPLATE -> return a new Template object representing that file else -> Log() +:= error message(s) raise IOError ] NB: On 2010-09-17 I discovered that the 'defTemplate' argument and attribute are never used except that the constructor stores them. .fileName [ as passed to constructor ] .modEpoch [ modification timestamp of fileName as epoch time ] .expand ( body, outFile ): [ if (body is a Body object) and (outFile is a writeable file handle) -> outFile +:= expansion of body using self as a template Log() +:= errors from expansion, if any ] State/Invariants: .__tokenList: [ a list of TToken objects representing the atoms of self ] .__openCondStack: [ a stack of OpenCond objects representing open conditional constructs during scanning ] .__ifStack: [ an IfStack object representing open conditional constructs during expansion ] cCsp||_||_g|_g|_tƒ|_tƒiƒ}|iƒ|tƒiƒjo t d‚ndS(s#Constructor for a Template s%The template file was not error-free.N( RRt_Template__tokenListt_Template__openCondStacktIfStackt_Template__ifStacktLogtcountt_Template__readFiletIOError(R RRterrCount((s%/u/john/projects/pystyler/template.pyR !s      cCs t|itƒofy1t|iƒ}ti|iƒ}|i|_Wq‘tj o"tƒi d|iƒdSq‘Xnt|iƒ}d|_ x|i p|i |ƒq”Wt |iƒdjo5x2|iD]#}|i d|i|ifƒqÐWn|iƒdS(s€Read a template file and store it in self. [ if self.fileName names a readable, valid template file -> self.__tokenList +:= a sequence of TToken objects representing the contents of that file self.__openCondStack := self.__openCondStack adjusted according to conditionals in that file else -> self.__tokenList +:= valid TTokens, if any self.__openCondStack := self.__openCondStack adjusted according to valid conditionals in that file Log() +:= error message(s) ] s*Can't open template file `%s' for reading.NlisCA conditional `%%%s;' construct that starts on line %d is unclosed.(t isinstanceRtstrtScantpathinfotPathInfotmodEpochR_R\terrort modeEpocht atEndFilet_Template__readTokenR,RYtnameR5tclose(R RtinfotopenCond((s%/u/john/projects/pystyler/template.pyt __readFile@s&       cCs2|itƒo|i|ƒn|i|ƒdS(süRead and store the next token from scan. [ if scan starts with a valid format code -> scan := scan advanced past that format code self.__tokenList +:= a TToken object representing that code self.__openCondStack := self.__openCondStack adjusted for that code, if necessary else if scan starts with a bad format code -> scan := scan advanced past the valid part, at least one Log() +:= error message else -> scan := scan advanced up to the next format code self.__tokenList +:= a TToken representing all text up to the next format code or EOF ] N(ttabMatcht START_FORMATt_Template__readFormatt_Template__readLiteral(R R((s%/u/john/projects/pystyler/template.pyt __readToken–s  cCsbg}d}x|p|i||ƒ}qWt|i|ti|dƒƒ}|ii|ƒdS(s5Copy literal text up to the next format code or EOF. [ scan := scan advanced up to the next START_FORMAT or EOF self.__tokenList +:= characters from scan up to the next START_FORMAT or EOF ] iRJN(t_Template__readLiteralLineR!RYtstringtjoinRXR6(R RtLtdonettoken((s%/u/john/projects/pystyler/template.pyt __readLiteralÐs    cCs|idƒ}|djo1|i|idƒƒ|idƒ|iƒn|i|i|ƒƒdS|iodSndSdS(sÂAdd to L text up to the next START_FORMAT, EOL, or EOF. [ if (scan is a Scan object) and (L is a list) -> if line in scan contains a START_FORMAT -> scan := scan advanced up to the first START_FORMAT L +:= text from scan up to the first START_FORMAT return 1 else if line in scan is the last in the file -> scan := scan advanced to EOF L +:= remaining text in scan return 1 else -> scan := scan advanced to the next line L +:= (remaining text on line in scan) + " " return 0 ] Riÿÿÿÿs iiN(tfindR R6ttabtnextLineRi(R RRxtendp((s%/u/john/projects/pystyler/template.pyt__readLiteralLines   R(R%telsetendifR3R=R@RCt nexttitletnexturlt prevtitletprevurltseealsoRROtuptitletupurlturlcCs%|itƒo'|iit|i|tƒƒdSn|itƒoti |i tƒƒ}n|i dƒdS|idƒp|i dt ƒdSny3|i |}||i|ƒ}|ii|ƒWnNtj o!|i dt|t fƒn#tj o}|i |ƒnXdS(s Process a template format code and append its token to self. [ if (scan is a Scan object) -> if scan starts with START_FORMAT -> scan := scan advanced one self.__tokenList +:= a literal TToken containing START_FORMAT else if scan starts with a valid format code name followed by END_FORMAT -> scan := scan advanced past all that self.__tokenList +:= a TToken representing that format code self.__openCondStack := self.__openCondStack adjusted for that format code else -> scan := scan advanced past any leading characters in FORMAT_NAME_CSET, then END_FORMAT if present Log() +:= error message ] Ns/The `%' must be followed by a format code name.Rs.The format code name must be followed by `%s'.s)There is no such format code as `%s%s%s'.(RpRqRXR6R!RYtanytFORMAT_NAME_CSETRvtlowerttabManyRgt END_FORMATtformatScannersR R-(R Rtgit constructorRztdetail((s%/u/john/projects/pystyler/template.pyt __readFormatIs*       cCs^xW|iD]L}y|i|i||ƒWq tj o}tƒid|ƒq Xq WdS(s?Expand all tokens in the context of a given body file. sCError in expanding conditional template item (shouldn't happen): %sN(RXR&R[R-R\Rg(R R%RRzR“((s%/u/john/projects/pystyler/template.pyR&™s   N(RRRR R R^RjRsRuR'R)R+R0R2R<R?RBRERFRGRHRIRMRNRPRQRSRRrR&(((s%/u/john/projects/pystyler/template.pyRûs6"  V : 1 4                   PRZcBs;eZdZd„Zd„Zd„Zd„Zd„ZRS(s6Represents the state of conditional template expansion. This class is basically a list of Condition objects, treated as a stack with the base at 0. Exports: IfStack() [ return a new, empty IfStack object ] .start(cond): [ if (cond is a Condition object) -> self := self with cond pushed on the stack ] .end() [ if self's stack contains at least one element -> self := self with the top condition popped return that condition else -> raise SyntaxError ] .elseCheck() [ if self's stack is empty -> raise SyntaxError else if self's top element has seen an else -> raise SyntaxError else -> self := self with the top Condition object's skipping state inverted and marked as having seen an `%else;' ] .isSkipping(): [ if self's state implies that we are currently skipping tokens -> return 1 else -> return 0 ] State/Invariants: .__stack: [ a list of Condition objects, used as a stack with the base at element 0, representing the nested %if-condition constructs at the current point of template expansion ] cCs g|_dS(s"IfStack class constructor N(t_IfStack__stack(R ((s%/u/john/projects/pystyler/template.pyR ØscCs|ii|ƒdS(s*Push a new condition on the stack N(R•R6(R tcond((s%/u/john/projects/pystyler/template.pyR9àscCsBt|iƒdjo td‚n|id}|id=|SdS(s&Pop a condition off the stack isMismatched `%end;' conditionaliÿÿÿÿN(R,R•R-(R R((s%/u/john/projects/pystyler/template.pyR1ès    cCs>t|iƒdjo td‚n|id}|iƒdS(s<Reverse the sense of the top condition on the stack isMismatched `%else;' conditionaliÿÿÿÿN(R,R•R-R.(R R/((s%/u/john/projects/pystyler/template.pyR.õs cCsHt|iƒdjodSnx#|iD]}|iodSq(q(WdS(sBChecks to see if we're expanding tokens or skipping them. ii(R,R•R;(R R–((s%/u/john/projects/pystyler/template.pyR# s   (RRRR R9R1R.R#(((s%/u/john/projects/pystyler/template.pyRZ±s "   R4cBs eZdZd„Zd„ZRS(sñAn instance tracks the scanning of one "%if...;" construct. Exports: OpenCond ( lineNo, name ): [ if (lineNo is the line number where the construct starts) and (name is the name of the construct, e.g., "ifnext" for the "%ifnext;" construct) -> return a new OpenCond with those values ] .lineNo [ as passed to constructor ] .name [ as passed to constroctor ] .hasElse [ if an "%else;" construct has been scanned that matches the opening "%if...;" -> 1 else -> 0 ] .elseCheck(): [ if self.hasElse -> raise SyntaxError else -> self.hasElse := 1 ] cCs||_||_d|_dS(s$Constructor for an OpenCond iN(R5RkthasElse(R R5Rk((s%/u/john/projects/pystyler/template.pyR ;s  cCs4|iotd|i|if‚n d|_dS(sDCheck to see if an "%else;" is legal in the current context sDOnly one `%else;' clause is allowed to match the `%%%s;' on line %d.iN(R—R-RkR5(R ((s%/u/john/projects/pystyler/template.pyR.Es (RRRR R.(((s%/u/john/projects/pystyler/template.pyR4!s R:cBs eZdZd„Zd„ZRS(s^An instance tracks the state of one conditional expansion. Exports: Condition ( skipping ): [ if skipping is 0 -> return a new Condition that says it is not skipping and has not seen an `%else;' if skipping is 1 -> return a new condition that says it *is* skipping and has not seen an `%else;' ] .skipping: [ if self is skipping -> 1 else -> 0 ] .hasElse: [ if self has seen an `%else;' -> 1 else -> 0 ] .elseCheck(): [ if self has seen an `%else' -> raise SyntaxError else if self.skipping is 1 -> self.skipping := 0 self.hasElse := 1 else -> self.skipping := 1 self.hasElse := 1 ] cCs||_d|_dS(s+Constructor for a Condition object iN(R;R—(R R;((s%/u/john/projects/pystyler/template.pyR ps cCsD|io td‚n*d|_|io d|_n d|_dS(sAProcess an `%else;' by inverting the sense of `skipping' s6Only one `%else;' can be used per `%if...;' construct.iiN(R—R-R;(R ((s%/u/john/projects/pystyler/template.pyR.ys    (RRRR R.(((s%/u/john/projects/pystyler/template.pyR:Rs ((RRvtsystlogRtcsetRdR%R RqRtlettersRŒR RRRR!R'R)R+R0R2R<R?RBRERFRGRHRIRMRNRPRQRSRRZR4R:(((s%/u/john/projects/pystyler/template.pyssJ      H[!  /#+,++*ÿ·p1