| | @@ -620,11 +620,10 @@ |
| 620 | 620 | ** Parse only Wiki links, return everything else as TOKEN_RAW. |
| 621 | 621 | ** |
| 622 | 622 | ** z points to the start of a token. Return the number of |
| 623 | 623 | ** characters in that token. Write the token type into *pTokenType. |
| 624 | 624 | */ |
| 625 | | - |
| 626 | 625 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 627 | 626 | int n; |
| 628 | 627 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 629 | 628 | *pTokenType = TOKEN_LINK; |
| 630 | 629 | return n; |
| | @@ -778,11 +777,11 @@ |
| 778 | 777 | static void popStack(Renderer *p){ |
| 779 | 778 | if( p->nStack ){ |
| 780 | 779 | int iCode; |
| 781 | 780 | p->nStack--; |
| 782 | 781 | iCode = p->aStack[p->nStack].iCode; |
| 783 | | - if( iCode!=MARKUP_DIV ){ |
| 782 | + if( iCode!=MARKUP_DIV && p->pOut ){ |
| 784 | 783 | blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName); |
| 785 | 784 | } |
| 786 | 785 | } |
| 787 | 786 | } |
| 788 | 787 | |
| | @@ -1447,5 +1446,186 @@ |
| 1447 | 1446 | if( z[i]!='<' ) return 0; |
| 1448 | 1447 | blob_init(pTitle, &z[iStart], i-iStart); |
| 1449 | 1448 | blob_init(pTail, &z[i+8], -1); |
| 1450 | 1449 | return 1; |
| 1451 | 1450 | } |
| 1451 | + |
| 1452 | +/* |
| 1453 | +** Parse text looking for wiki hyperlinks in one of the formats: |
| 1454 | +** |
| 1455 | +** [target] |
| 1456 | +** [target|...] |
| 1457 | +** |
| 1458 | +** Where "target" can be either an artifact ID prefix or a wiki page |
| 1459 | +** name. For each such hyperlink found, add an entry to the |
| 1460 | +** backlink table. |
| 1461 | +*/ |
| 1462 | +void wiki_extract_links( |
| 1463 | + char *z, /* The wiki text from which to extract links */ |
| 1464 | + int srcid, /* srcid field for new BACKLINK table entries */ |
| 1465 | + int srctype, /* srctype field for new BACKLINK table entries */ |
| 1466 | + double mtime, /* mtime field for new BACKLINK table entries */ |
| 1467 | + int replaceFlag, /* True first delete prior BACKLINK entries */ |
| 1468 | + int flags /* wiki parsing flags */ |
| 1469 | +){ |
| 1470 | + Renderer renderer; |
| 1471 | + int tokenType; |
| 1472 | + ParsedMarkup markup; |
| 1473 | + int n; |
| 1474 | + int inlineOnly; |
| 1475 | + int wikiUseHtml = 0; |
| 1476 | + |
| 1477 | + memset(&renderer, 0, sizeof(renderer)); |
| 1478 | + renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 1479 | + if( flags & WIKI_NOBLOCK ){ |
| 1480 | + renderer.state |= INLINE_MARKUP_ONLY; |
| 1481 | + } |
| 1482 | + if( db_get_int("wiki-use-html", 0) ){ |
| 1483 | + renderer.state |= WIKI_USE_HTML; |
| 1484 | + wikiUseHtml = 1; |
| 1485 | + } |
| 1486 | + inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; |
| 1487 | + if( replaceFlag ){ |
| 1488 | + db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d", |
| 1489 | + srctype, srcid); |
| 1490 | + } |
| 1491 | + |
| 1492 | + while( z[0] ){ |
| 1493 | + if( wikiUseHtml ){ |
| 1494 | + n = nextRawToken(z, &renderer, &tokenType); |
| 1495 | + }else{ |
| 1496 | + n = nextWikiToken(z, &renderer, &tokenType); |
| 1497 | + } |
| 1498 | + switch( tokenType ){ |
| 1499 | + case TOKEN_LINK: { |
| 1500 | + char *zTarget; |
| 1501 | + int i, c; |
| 1502 | + char zLink[42]; |
| 1503 | + |
| 1504 | + zTarget = &z[1]; |
| 1505 | + for(i=0; zTarget[i] && zTarget[i]!='|' && zTarget[i]!=']'; i++){} |
| 1506 | + while(i>1 && zTarget[i-1]==' '){ i--; } |
| 1507 | + c = zTarget[i]; |
| 1508 | + zTarget[i] = 0; |
| 1509 | + if( is_valid_uuid(zTarget) ){ |
| 1510 | + memcpy(zLink, zTarget, i+1); |
| 1511 | + canonical16(zLink, i); |
| 1512 | + db_multi_exec( |
| 1513 | + "REPLACE INTO backlink(target,srctype,srcid,mtime)" |
| 1514 | + "VALUES(%Q,%d,%d,%g)", zLink, srctype, srcid, mtime |
| 1515 | + ); |
| 1516 | + } |
| 1517 | + zTarget[i] = c; |
| 1518 | + break; |
| 1519 | + } |
| 1520 | + case TOKEN_MARKUP: { |
| 1521 | + const char *zId; |
| 1522 | + int iDiv; |
| 1523 | + parseMarkup(&markup, z); |
| 1524 | + |
| 1525 | + /* Markup of the form </div id=ID> where there is a matching |
| 1526 | + ** ID somewhere on the stack. Exit the verbatim if were are in |
| 1527 | + ** it. Pop the stack up to the matching <div>. Discard the |
| 1528 | + ** </div> |
| 1529 | + */ |
| 1530 | + if( markup.iCode==MARKUP_DIV && markup.endTag && |
| 1531 | + (zId = markupId(&markup))!=0 && |
| 1532 | + (iDiv = findTagWithId(&renderer, MARKUP_DIV, zId))>=0 |
| 1533 | + ){ |
| 1534 | + if( renderer.inVerbatim ){ |
| 1535 | + renderer.inVerbatim = 0; |
| 1536 | + renderer.state = renderer.preVerbState; |
| 1537 | + } |
| 1538 | + while( renderer.nStack>iDiv+1 ) popStack(&renderer); |
| 1539 | + if( renderer.aStack[iDiv].allowWiki ){ |
| 1540 | + renderer.state |= ALLOW_WIKI; |
| 1541 | + }else{ |
| 1542 | + renderer.state &= ~ALLOW_WIKI; |
| 1543 | + } |
| 1544 | + renderer.nStack--; |
| 1545 | + }else |
| 1546 | + |
| 1547 | + /* If within <verbatim id=ID> ignore everything other than |
| 1548 | + ** </verbatim id=ID> and the </dev id=ID2> above. |
| 1549 | + */ |
| 1550 | + if( renderer.inVerbatim ){ |
| 1551 | + if( endVerbatim(&renderer, &markup) ){ |
| 1552 | + renderer.inVerbatim = 0; |
| 1553 | + renderer.state = renderer.preVerbState; |
| 1554 | + }else{ |
| 1555 | + n = 1; |
| 1556 | + } |
| 1557 | + }else |
| 1558 | + |
| 1559 | + /* Render invalid markup literally. The markup appears in the |
| 1560 | + ** final output as plain text. |
| 1561 | + */ |
| 1562 | + if( markup.iCode==MARKUP_INVALID ){ |
| 1563 | + n = 1; |
| 1564 | + }else |
| 1565 | + |
| 1566 | + /* If the markup is not font-change markup ignore it if the |
| 1567 | + ** font-change-only flag is set. |
| 1568 | + */ |
| 1569 | + if( (markup.iType&MUTYPE_FONT)==0 && |
| 1570 | + (renderer.state & FONT_MARKUP_ONLY)!=0 ){ |
| 1571 | + /* Do nothing */ |
| 1572 | + }else |
| 1573 | + |
| 1574 | + if( markup.iCode==MARKUP_NOWIKI ){ |
| 1575 | + if( markup.endTag ){ |
| 1576 | + renderer.state |= ALLOW_WIKI; |
| 1577 | + }else{ |
| 1578 | + renderer.state &= ~ALLOW_WIKI; |
| 1579 | + } |
| 1580 | + }else |
| 1581 | + |
| 1582 | + /* Ignore block markup for in-line rendering. |
| 1583 | + */ |
| 1584 | + if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){ |
| 1585 | + /* Do nothing */ |
| 1586 | + }else |
| 1587 | + |
| 1588 | + /* Generate end-tags */ |
| 1589 | + if( markup.endTag ){ |
| 1590 | + popStackToTag(&renderer, markup.iCode); |
| 1591 | + }else |
| 1592 | + |
| 1593 | + /* Push <div> markup onto the stack together with the id=ID attribute. |
| 1594 | + */ |
| 1595 | + if( markup.iCode==MARKUP_DIV ){ |
| 1596 | + pushStackWithId(&renderer, markup.iCode, markupId(&markup), |
| 1597 | + (renderer.state & ALLOW_WIKI)!=0); |
| 1598 | + }else |
| 1599 | + |
| 1600 | + /* Enter <verbatim> processing. With verbatim enabled, all other |
| 1601 | + ** markup other than the corresponding end-tag with the same ID is |
| 1602 | + ** ignored. |
| 1603 | + */ |
| 1604 | + if( markup.iCode==MARKUP_VERBATIM ){ |
| 1605 | + int vAttrIdx, vAttrDidAppend=0; |
| 1606 | + renderer.zVerbatimId = 0; |
| 1607 | + renderer.inVerbatim = 1; |
| 1608 | + renderer.preVerbState = renderer.state; |
| 1609 | + renderer.state &= ~ALLOW_WIKI; |
| 1610 | + for (vAttrIdx = 0; vAttrIdx < markup.nAttr; vAttrIdx++){ |
| 1611 | + if( markup.aAttr[vAttrIdx].iACode == ATTR_ID ){ |
| 1612 | + renderer.zVerbatimId = markup.aAttr[0].zValue; |
| 1613 | + }else if( markup.aAttr[vAttrIdx].iACode == ATTR_TYPE ){ |
| 1614 | + vAttrDidAppend=1; |
| 1615 | + } |
| 1616 | + } |
| 1617 | + renderer.wantAutoParagraph = 0; |
| 1618 | + } |
| 1619 | + |
| 1620 | + /* Restore the input text to its original configuration |
| 1621 | + */ |
| 1622 | + unparseMarkup(&markup); |
| 1623 | + break; |
| 1624 | + } |
| 1625 | + default: { |
| 1626 | + break; |
| 1627 | + } |
| 1628 | + } |
| 1629 | + z += n; |
| 1630 | + } |
| 1631 | +} |
| 1452 | 1632 | |