Navigation

Google Docs

Chapter 4 : Code Snippets for Apps Script integration with Google Docs (Bounded script)

สร้าง API ดึงข้อมูลจาก Doc มาเป็น Markdown

1/**
2 * Handles GET requests to the web app.
3 * Expects a 'docId' parameter and returns the document's content as Markdown.
4 */
5function doGet(e) {
6  try {
7    // Validate the presence of 'docId' parameter
8    if (!e.parameter.docId) {
9      return ContentService.createTextOutput(JSON.stringify({
10        success: false,
11        error: "Missing 'docId' parameter."
12      })).setMimeType(ContentService.MimeType.JSON);
13    }
14
15    var docId = e.parameter.docId;
16    var doc;
17    try {
18      doc = DocumentApp.openById(docId);
19    } catch (err) {
20      return ContentService.createTextOutput(JSON.stringify({
21        success: false,
22        error: "Invalid 'docId' or insufficient permissions."
23      })).setMimeType(ContentService.MimeType.JSON);
24    }
25
26    // Document title
27    var docTitle = doc.getName();
28
29    // Get all top-level tabs
30    var topLevelTabs = doc.getTabs();
31    var tabsData = topLevelTabs.map(function(tab) {
32      return traverseTabAsMarkdown(tab);
33    });
34
35    // Structure the response
36    var response = {
37      success: true,
38      document: {
39        id: docId,
40        title: docTitle,
41        tabs: tabsData
42      }
43    };
44
45    return ContentService
46      .createTextOutput(JSON.stringify(response))
47      .setMimeType(ContentService.MimeType.JSON);
48
49  } catch (error) {
50    return ContentService
51      .createTextOutput(JSON.stringify({
52        success: false,
53        error: "An unexpected error occurred.",
54        details: error.toString()
55      }))
56      .setMimeType(ContentService.MimeType.JSON);
57  }
58}
59
60/**
61 * Recursively traverse a tab and build a Markdown string from its content.
62 * Also handle any child tabs.
63 *
64 * @param {Tab} tab - The Tab object to traverse.
65 * @return {Object} - { id, title, content } with markdown content
66 */
67function traverseTabAsMarkdown(tab) {
68  var tabId = tab.getId();
69  var tabTitle = tab.getTitle();
70
71  var documentTab = tab.asDocumentTab();
72  var body = documentTab.getBody();
73
74  // Convert the body to a Markdown string
75  var markdownContent = bodyToMarkdown(body);
76
77  // Recursively process child tabs
78  var childTabs = tab.getChildTabs();
79  var subTabs = childTabs.map(function(childTab) {
80    return traverseTabAsMarkdown(childTab);
81  });
82
83  return {
84    id: tabId,
85    title: tabTitle,
86    content: markdownContent,
87    subTabs: subTabs
88  };
89}
90
91/**
92 * Converts the entire Body of a DocumentTab into a single Markdown string,
93 * ignoring headings beyond HEADING2 and converting bold text to **bold**.
94 *
95 * @param {Body} body - The DocumentTab's body.
96 * @return {string} - Markdown representation of the content.
97 */
98function bodyToMarkdown(body) {
99  var markdown = [];
100  var numChildren = body.getNumChildren();
101
102  for (var i = 0; i < numChildren; i++) {
103    var element = body.getChild(i);
104    if (element.getType() === DocumentApp.ElementType.PARAGRAPH) {
105      var paragraph = element.asParagraph();
106      var heading = paragraph.getHeading();
107
108      // Convert the paragraph to a Markdown line (with possible bold text)
109      var mdLine = paragraphToMarkdown(paragraph);
110
111      // Decide how to label the line based on heading level
112      if (heading === DocumentApp.ParagraphHeading.HEADING1) {
113        // # Heading
114        mdLine = "# " + mdLine;
115      } else if (heading === DocumentApp.ParagraphHeading.HEADING2) {
116        // ## Heading
117        mdLine = "## " + mdLine;
118      } else if (heading === DocumentApp.ParagraphHeading.HEADING3 ) {
119        mdLine = '###' + mdLine
120      } else if 
121      (
122        heading === DocumentApp.ParagraphHeading.HEADING4 ||
123        heading === DocumentApp.ParagraphHeading.HEADING5 ||
124        heading === DocumentApp.ParagraphHeading.HEADING6
125      ) {
126     
127        continue; 
128      }
129
130      // Append to our final Markdown
131      // (skip entirely if blank—often empty paragraphs occur)
132      if (mdLine.trim() !== "") {
133        markdown.push(mdLine);
134      }
135    }
136    // Note: ignoring tables, images, etc. as requested
137  }
138
139  // Combine into a single string with newlines
140  return markdown.join("\n\n");
141}
142
143/**
144 * Converts a single paragraph into a Markdown string, detecting bold text.
145 * Everything else (italics, underline, etc.) is left plain by default.
146 *
147 * @param {Paragraph} paragraph - The paragraph to process.
148 * @return {string} - The paragraph text with **bold** segments in Markdown.
149 */
150function paragraphToMarkdown(paragraph) {
151  var mdParts = [];
152  var numChild = paragraph.getNumChildren();
153
154  for (var i = 0; i < numChild; i++) {
155    var child = paragraph.getChild(i);
156    if (child.getType() === DocumentApp.ElementType.TEXT) {
157      var textElement = child.asText();
158      var textStyle = textElement.getTextAttributeIndices();
159      // We'll iterate by style runs
160      for (var s = 0; s < textStyle.length; s++) {
161        var startOffset = textStyle[s];
162        var endOffset = (s === textStyle.length - 1)
163          ? textElement.getText().length - 1
164          : textStyle[s + 1] - 1;
165
166        var segment = textElement.getText().substring(startOffset, endOffset + 1);
167
168        // Check if this range is bold
169        var isBold = textElement.isBold(startOffset);
170
171        if (isBold) {
172          mdParts.push("**" + segment + "**");
173        } else {
174          mdParts.push(segment);
175        }
176      }
177    }
178  }
179
180  // Join all text/bold runs into one line
181  return mdParts.join("");
182}
183