Commit 0ab45001b52ddc79d0e9742e5f38b842f3216c3e
1 parent
4a2a8283
Refactor JSON schema handling in QPDFJob::json_schema
Consolidate and replace inline JSON schema strings with static constexpr constants for better organization and maintainability. Functionality remains unchanged.
Showing
1 changed file
with
171 additions
and
159 deletions
libqpdf/QPDFJob.cc
| ... | ... | @@ -1389,6 +1389,167 @@ QPDFJob::json_schema(int json_version, std::set<std::string>* keys) |
| 1389 | 1389 | // mismatch is a bug in qpdf. This helps to enforce our policy of consistently providing a known |
| 1390 | 1390 | // structure where every documented key will always be present, which makes it easier to consume |
| 1391 | 1391 | // our JSON. This is discussed in more depth in the manual. |
| 1392 | + | |
| 1393 | + static constexpr const char* objects_schema_v1 = R"({ | |
| 1394 | + "<n n R|trailer>": "json representation of object" | |
| 1395 | + })"; | |
| 1396 | + | |
| 1397 | + static constexpr const char* objectinfo_schema_v1 = R"({ | |
| 1398 | + "<object-id>": { | |
| 1399 | + "stream": { | |
| 1400 | + "filter": "if stream, its filters, otherwise null", | |
| 1401 | + "is": "whether the object is a stream", | |
| 1402 | + "length": "if stream, its length, otherwise null" | |
| 1403 | + } | |
| 1404 | + } | |
| 1405 | + })"; | |
| 1406 | + | |
| 1407 | + static constexpr const char* qpdf_schema = R"([{ | |
| 1408 | + "jsonversion": "numeric JSON version", | |
| 1409 | + "pdfversion": "PDF version as x.y", | |
| 1410 | + "pushedinheritedpageresources": "whether inherited attributes were pushed to the page level", | |
| 1411 | + "calledgetallpages": "whether getAllPages was called", | |
| 1412 | + "maxobjectid": "highest object ID in output, ignored on input" | |
| 1413 | + }, | |
| 1414 | + { | |
| 1415 | + "<obj:n n R|trailer>": "json representation of object" | |
| 1416 | + }])"; | |
| 1417 | + | |
| 1418 | + static constexpr const char* pages_schema = R"([ | |
| 1419 | + { | |
| 1420 | + "contents": [ | |
| 1421 | + "reference to each content stream" | |
| 1422 | + ], | |
| 1423 | + "images": [ | |
| 1424 | + { | |
| 1425 | + "bitspercomponent": "bits per component", | |
| 1426 | + "colorspace": "color space", | |
| 1427 | + "decodeparms": [ | |
| 1428 | + "decode parameters for image data" | |
| 1429 | + ], | |
| 1430 | + "filter": [ | |
| 1431 | + "filters applied to image data" | |
| 1432 | + ], | |
| 1433 | + "filterable": "whether image data can be decoded using the decode level qpdf was invoked with", | |
| 1434 | + "height": "image height", | |
| 1435 | + "name": "name of image in XObject table", | |
| 1436 | + "object": "reference to image stream", | |
| 1437 | + "width": "image width" | |
| 1438 | + } | |
| 1439 | + ], | |
| 1440 | + "label": "page label dictionary, or null if none", | |
| 1441 | + "object": "reference to original page object", | |
| 1442 | + "outlines": [ | |
| 1443 | + { | |
| 1444 | + "dest": "outline destination dictionary", | |
| 1445 | + "object": "reference to outline that targets this page", | |
| 1446 | + "title": "outline title" | |
| 1447 | + } | |
| 1448 | + ], | |
| 1449 | + "pageposfrom1": "position of page in document numbering from 1" | |
| 1450 | + } | |
| 1451 | + ])"; | |
| 1452 | + | |
| 1453 | + static constexpr const char* pagelabels_schema = R"([ | |
| 1454 | + { | |
| 1455 | + "index": "starting page position starting from zero", | |
| 1456 | + "label": "page label dictionary" | |
| 1457 | + } | |
| 1458 | + ])"; | |
| 1459 | + | |
| 1460 | + static constexpr const char* outlines_schema = R"([ | |
| 1461 | + { | |
| 1462 | + "dest": "outline destination dictionary", | |
| 1463 | + "destpageposfrom1": "position of destination page in document numbered from 1; null if not known", | |
| 1464 | + "kids": "array of descendent outlines", | |
| 1465 | + "object": "reference to this outline", | |
| 1466 | + "open": "whether the outline is displayed expanded", | |
| 1467 | + "title": "outline title" | |
| 1468 | + } | |
| 1469 | + ])"; | |
| 1470 | + | |
| 1471 | + static constexpr const char* acroform_schema = R"({ | |
| 1472 | + "fields": [ | |
| 1473 | + { | |
| 1474 | + "alternativename": "alternative name of field -- this is the one usually shown to users", | |
| 1475 | + "annotation": { | |
| 1476 | + "annotationflags": "annotation flags from /F -- see pdf_annotation_flag_e in qpdf/Constants.h", | |
| 1477 | + "appearancestate": "appearance state -- can be used to determine value for checkboxes and radio buttons", | |
| 1478 | + "object": "reference to the annotation object" | |
| 1479 | + }, | |
| 1480 | + "choices": "for choices fields, the list of choices presented to the user", | |
| 1481 | + "defaultvalue": "default value of field", | |
| 1482 | + "fieldflags": "form field flags from /Ff -- see pdf_form_field_flag_e in qpdf/Constants.h", | |
| 1483 | + "fieldtype": "field type", | |
| 1484 | + "fullname": "full name of field", | |
| 1485 | + "ischeckbox": "whether field is a checkbox", | |
| 1486 | + "ischoice": "whether field is a list, combo, or dropdown", | |
| 1487 | + "isradiobutton": "whether field is a radio button -- buttons in a single group share a parent", | |
| 1488 | + "istext": "whether field is a text field", | |
| 1489 | + "mappingname": "mapping name of field", | |
| 1490 | + "object": "reference to this form field", | |
| 1491 | + "pageposfrom1": "position of containing page numbered from 1", | |
| 1492 | + "parent": "reference to this field's parent", | |
| 1493 | + "partialname": "partial name of field", | |
| 1494 | + "quadding": "field quadding -- number indicating left, center, or right", | |
| 1495 | + "value": "value of field" | |
| 1496 | + } | |
| 1497 | + ], | |
| 1498 | + "hasacroform": "whether the document has interactive forms", | |
| 1499 | + "needappearances": "whether the form fields' appearance streams need to be regenerated" | |
| 1500 | + })"; | |
| 1501 | + | |
| 1502 | + static constexpr const char* encrypt_schema1 = R"({ | |
| 1503 | + "capabilities": { | |
| 1504 | + "accessibility": "allow extraction for accessibility?", | |
| 1505 | + "extract": "allow extraction?", | |
| 1506 | + ")"; | |
| 1507 | + | |
| 1508 | + static constexpr const char* encrypt_schema2 = R"(": "allow modifying annotations?", | |
| 1509 | + "modify": "allow all modifications?", | |
| 1510 | + "modifyassembly": "allow modifying document assembly?", | |
| 1511 | + "modifyforms": "allow modifying forms?", | |
| 1512 | + "modifyother": "allow other modifications?", | |
| 1513 | + "printhigh": "allow high resolution printing?", | |
| 1514 | + "printlow": "allow low resolution printing?" | |
| 1515 | + }, | |
| 1516 | + "encrypted": "whether the document is encrypted", | |
| 1517 | + "ownerpasswordmatched": "whether supplied password matched owner password; always false for non-encrypted files", | |
| 1518 | + "recovereduserpassword": "If the owner password was used to recover the user password, reveal user password; otherwise null", | |
| 1519 | + "parameters": { | |
| 1520 | + "P": "P value from Encrypt dictionary", | |
| 1521 | + "R": "R value from Encrypt dictionary", | |
| 1522 | + "V": "V value from Encrypt dictionary", | |
| 1523 | + "bits": "encryption key bit length", | |
| 1524 | + "filemethod": "encryption method for attachments", | |
| 1525 | + "key": "encryption key; will be null unless --show-encryption-key was specified", | |
| 1526 | + "method": "overall encryption method: none, mixed, RC4, AESv2, AESv3", | |
| 1527 | + "streammethod": "encryption method for streams", | |
| 1528 | + "stringmethod": "encryption method for string" | |
| 1529 | + }, | |
| 1530 | + "userpasswordmatched": "whether supplied password matched user password; always false for non-encrypted files" | |
| 1531 | + })"; | |
| 1532 | + | |
| 1533 | + static constexpr const char* attachments_schema = R"({ | |
| 1534 | + "<attachment-key>": { | |
| 1535 | + "filespec": "object containing the file spec", | |
| 1536 | + "preferredcontents": "most preferred embedded file stream", | |
| 1537 | + "preferredname": "most preferred file name", | |
| 1538 | + "description": "description of attachment", | |
| 1539 | + "names": { | |
| 1540 | + "<name-key>": "file name for key" | |
| 1541 | + }, | |
| 1542 | + "streams": { | |
| 1543 | + "<stream-key>": { | |
| 1544 | + "creationdate": "ISO-8601 creation date or null", | |
| 1545 | + "modificationdate": "ISO-8601 modification date or null", | |
| 1546 | + "mimetype": "mime type or null", | |
| 1547 | + "checksum": "MD5 checksum or null" | |
| 1548 | + } | |
| 1549 | + } | |
| 1550 | + } | |
| 1551 | + })"; | |
| 1552 | + | |
| 1392 | 1553 | JSON schema = JSON::makeDictionary(); |
| 1393 | 1554 | schema.addDictionaryMember( |
| 1394 | 1555 | "version", |
| ... | ... | @@ -1408,169 +1569,20 @@ QPDFJob::json_schema(int json_version, std::set<std::string>* keys) |
| 1408 | 1569 | // The list of selectable top-level keys id duplicated in the following places: job.yml, |
| 1409 | 1570 | // QPDFJob::json_schema, and QPDFJob::doJSON. |
| 1410 | 1571 | if (json_version == 1) { |
| 1411 | - add_if_want_key("objects", R"({ | |
| 1412 | - "<n n R|trailer>": "json representation of object" | |
| 1413 | -})"); | |
| 1414 | - | |
| 1415 | - add_if_want_key("objectinfo", R"({ | |
| 1416 | - "<object-id>": { | |
| 1417 | - "stream": { | |
| 1418 | - "filter": "if stream, its filters, otherwise null", | |
| 1419 | - "is": "whether the object is a stream", | |
| 1420 | - "length": "if stream, its length, otherwise null" | |
| 1421 | - } | |
| 1422 | - } | |
| 1423 | -})"); | |
| 1424 | - | |
| 1572 | + add_if_want_key("objects", objects_schema_v1); | |
| 1573 | + add_if_want_key("objectinfo", objectinfo_schema_v1); | |
| 1425 | 1574 | } else { |
| 1426 | - add_if_want_key("qpdf", R"([{ | |
| 1427 | - "jsonversion": "numeric JSON version", | |
| 1428 | - "pdfversion": "PDF version as x.y", | |
| 1429 | - "pushedinheritedpageresources": "whether inherited attributes were pushed to the page level", | |
| 1430 | - "calledgetallpages": "whether getAllPages was called", | |
| 1431 | - "maxobjectid": "highest object ID in output, ignored on input" | |
| 1432 | -}, | |
| 1433 | -{ | |
| 1434 | - "<obj:n n R|trailer>": "json representation of object" | |
| 1435 | -}])"); | |
| 1436 | - } | |
| 1437 | - add_if_want_key("pages", R"([ | |
| 1438 | - { | |
| 1439 | - "contents": [ | |
| 1440 | - "reference to each content stream" | |
| 1441 | - ], | |
| 1442 | - "images": [ | |
| 1443 | - { | |
| 1444 | - "bitspercomponent": "bits per component", | |
| 1445 | - "colorspace": "color space", | |
| 1446 | - "decodeparms": [ | |
| 1447 | - "decode parameters for image data" | |
| 1448 | - ], | |
| 1449 | - "filter": [ | |
| 1450 | - "filters applied to image data" | |
| 1451 | - ], | |
| 1452 | - "filterable": "whether image data can be decoded using the decode level qpdf was invoked with", | |
| 1453 | - "height": "image height", | |
| 1454 | - "name": "name of image in XObject table", | |
| 1455 | - "object": "reference to image stream", | |
| 1456 | - "width": "image width" | |
| 1457 | - } | |
| 1458 | - ], | |
| 1459 | - "label": "page label dictionary, or null if none", | |
| 1460 | - "object": "reference to original page object", | |
| 1461 | - "outlines": [ | |
| 1462 | - { | |
| 1463 | - "dest": "outline destination dictionary", | |
| 1464 | - "object": "reference to outline that targets this page", | |
| 1465 | - "title": "outline title" | |
| 1466 | - } | |
| 1467 | - ], | |
| 1468 | - "pageposfrom1": "position of page in document numbering from 1" | |
| 1469 | - } | |
| 1470 | -])"); | |
| 1471 | - | |
| 1472 | - add_if_want_key("pagelabels", R"([ | |
| 1473 | - { | |
| 1474 | - "index": "starting page position starting from zero", | |
| 1475 | - "label": "page label dictionary" | |
| 1476 | - } | |
| 1477 | -])"); | |
| 1478 | - | |
| 1479 | - add_if_want_key("outlines", R"([ | |
| 1480 | - { | |
| 1481 | - "dest": "outline destination dictionary", | |
| 1482 | - "destpageposfrom1": "position of destination page in document numbered from 1; null if not known", | |
| 1483 | - "kids": "array of descendent outlines", | |
| 1484 | - "object": "reference to this outline", | |
| 1485 | - "open": "whether the outline is displayed expanded", | |
| 1486 | - "title": "outline title" | |
| 1487 | - } | |
| 1488 | -])"); | |
| 1489 | - | |
| 1490 | - add_if_want_key("acroform", R"({ | |
| 1491 | - "fields": [ | |
| 1492 | - { | |
| 1493 | - "alternativename": "alternative name of field -- this is the one usually shown to users", | |
| 1494 | - "annotation": { | |
| 1495 | - "annotationflags": "annotation flags from /F -- see pdf_annotation_flag_e in qpdf/Constants.h", | |
| 1496 | - "appearancestate": "appearance state -- can be used to determine value for checkboxes and radio buttons", | |
| 1497 | - "object": "reference to the annotation object" | |
| 1498 | - }, | |
| 1499 | - "choices": "for choices fields, the list of choices presented to the user", | |
| 1500 | - "defaultvalue": "default value of field", | |
| 1501 | - "fieldflags": "form field flags from /Ff -- see pdf_form_field_flag_e in qpdf/Constants.h", | |
| 1502 | - "fieldtype": "field type", | |
| 1503 | - "fullname": "full name of field", | |
| 1504 | - "ischeckbox": "whether field is a checkbox", | |
| 1505 | - "ischoice": "whether field is a list, combo, or dropdown", | |
| 1506 | - "isradiobutton": "whether field is a radio button -- buttons in a single group share a parent", | |
| 1507 | - "istext": "whether field is a text field", | |
| 1508 | - "mappingname": "mapping name of field", | |
| 1509 | - "object": "reference to this form field", | |
| 1510 | - "pageposfrom1": "position of containing page numbered from 1", | |
| 1511 | - "parent": "reference to this field's parent", | |
| 1512 | - "partialname": "partial name of field", | |
| 1513 | - "quadding": "field quadding -- number indicating left, center, or right", | |
| 1514 | - "value": "value of field" | |
| 1515 | - } | |
| 1516 | - ], | |
| 1517 | - "hasacroform": "whether the document has interactive forms", | |
| 1518 | - "needappearances": "whether the form fields' appearance streams need to be regenerated" | |
| 1519 | -})"); | |
| 1575 | + add_if_want_key("qpdf", qpdf_schema); | |
| 1576 | + } | |
| 1577 | + add_if_want_key("pages", pages_schema); | |
| 1578 | + add_if_want_key("pagelabels", pagelabels_schema); | |
| 1579 | + add_if_want_key("outlines", outlines_schema); | |
| 1580 | + add_if_want_key("acroform", acroform_schema); | |
| 1520 | 1581 | |
| 1521 | 1582 | std::string MODIFY_ANNOTATIONS = |
| 1522 | 1583 | (json_version == 1 ? "moddifyannotations" : "modifyannotations"); |
| 1523 | - add_if_want_key( | |
| 1524 | - "encrypt", | |
| 1525 | - R"({ | |
| 1526 | - "capabilities": { | |
| 1527 | - "accessibility": "allow extraction for accessibility?", | |
| 1528 | - "extract": "allow extraction?", | |
| 1529 | - ")" + MODIFY_ANNOTATIONS + | |
| 1530 | - R"(": "allow modifying annotations?", | |
| 1531 | - "modify": "allow all modifications?", | |
| 1532 | - "modifyassembly": "allow modifying document assembly?", | |
| 1533 | - "modifyforms": "allow modifying forms?", | |
| 1534 | - "modifyother": "allow other modifications?", | |
| 1535 | - "printhigh": "allow high resolution printing?", | |
| 1536 | - "printlow": "allow low resolution printing?" | |
| 1537 | - }, | |
| 1538 | - "encrypted": "whether the document is encrypted", | |
| 1539 | - "ownerpasswordmatched": "whether supplied password matched owner password; always false for non-encrypted files", | |
| 1540 | - "recovereduserpassword": "If the owner password was used to recover the user password, reveal user password; otherwise null", | |
| 1541 | - "parameters": { | |
| 1542 | - "P": "P value from Encrypt dictionary", | |
| 1543 | - "R": "R value from Encrypt dictionary", | |
| 1544 | - "V": "V value from Encrypt dictionary", | |
| 1545 | - "bits": "encryption key bit length", | |
| 1546 | - "filemethod": "encryption method for attachments", | |
| 1547 | - "key": "encryption key; will be null unless --show-encryption-key was specified", | |
| 1548 | - "method": "overall encryption method: none, mixed, RC4, AESv2, AESv3", | |
| 1549 | - "streammethod": "encryption method for streams", | |
| 1550 | - "stringmethod": "encryption method for string" | |
| 1551 | - }, | |
| 1552 | - "userpasswordmatched": "whether supplied password matched user password; always false for non-encrypted files" | |
| 1553 | -})"); | |
| 1554 | - | |
| 1555 | - add_if_want_key("attachments", R"({ | |
| 1556 | - "<attachment-key>": { | |
| 1557 | - "filespec": "object containing the file spec", | |
| 1558 | - "preferredcontents": "most preferred embedded file stream", | |
| 1559 | - "preferredname": "most preferred file name", | |
| 1560 | - "description": "description of attachment", | |
| 1561 | - "names": { | |
| 1562 | - "<name-key>": "file name for key" | |
| 1563 | - }, | |
| 1564 | - "streams": { | |
| 1565 | - "<stream-key>": { | |
| 1566 | - "creationdate": "ISO-8601 creation date or null", | |
| 1567 | - "modificationdate": "ISO-8601 modification date or null", | |
| 1568 | - "mimetype": "mime type or null", | |
| 1569 | - "checksum": "MD5 checksum or null" | |
| 1570 | - } | |
| 1571 | - } | |
| 1572 | - } | |
| 1573 | -})"); | |
| 1584 | + add_if_want_key("encrypt", encrypt_schema1 + MODIFY_ANNOTATIONS + encrypt_schema2); | |
| 1585 | + add_if_want_key("attachments", attachments_schema); | |
| 1574 | 1586 | |
| 1575 | 1587 | return schema; |
| 1576 | 1588 | } | ... | ... |